web-dev-qa-db-ja.com

Kotlinコルーチンでのサスペンド機能の意味

私はコトリンコルーチンを読んでおり、それがsuspend関数に基づいていることを知っています。しかし、suspendはどういう意味ですか?

コルーチンまたは機能が停止しますか?

から https://kotlinlang.org/docs/reference/coroutines.html

基本的に、コルーチンはスレッドをブロックせずに中断できる計算です

人々はしばしば「機能を停止する」と言うのを聞きました。しかし、機能が終了するのを待っているために中断されるのはコルーチンだと思いますか? 「サスペンド」は通常、「操作の停止」を意味します。この場合、コルーチンはアイドル状態です。

????コルーチンが中断されていると言うべきですか?

どのコルーチンが中断されますか?

から https://kotlinlang.org/docs/reference/coroutines.html

類推を続けるために、await()は、何らかの計算が完了するまでコルーチンを中断し、その結果を返す中断関数(したがって、非同期{}ブロック内からも呼び出し可能)になります。

async { // Here I call it the outer async coroutine
    ...
    // Here I call computation the inner coroutine
    val result = computation.await()
    ...
}

???? 「なんらかの計算が完了するまでコルーチンを一時停止する」と書かれていますが、コルーチンは軽量スレッドのようなものです。コルーチンが中断された場合、どのように計算を実行できますか?

awaitcomputationで呼び出されるので、asyncを返すのはDeferredである可能性があります。つまり、別のコルーチンを開始できます

fun computation(): Deferred<Boolean> {
    return async {
        true
    }
}

????引用文は、コルーチンを一時停止すると言います。 suspend外側のasyncコルーチン、またはsuspend内側のcomputationコルーチンを意味しますか?

suspendは、外側のasyncコルーチンが内側のawaitコルーチンが終了するのを待っている間(computation)、それ(外側のasyncコルーチン)がアイドル状態であることを意味しますか(したがって、名前は一時停止し)スレッドをスレッドプールに返し、子computationコルーチンが終了すると、(外側のasyncコルーチン)が起動し、プールから別のスレッドを取得して続行しますか?

スレッドに言及した理由は、 https://kotlinlang.org/docs/tutorials/coroutines-basic-jvm.html

コルーチンが待機している間にスレッドがプールに戻され、待機が完了すると、プール内の空きスレッドでコルーチンが再開します

46
onmyway133

サスペンド機能はすべてのコルーチンの中心にあります。一時停止機能は、一時停止して後で再開できる機能です。長時間実行される操作を実行し、ブロックせずに完了するまで待つことができます。

一時停止機能の構文は、一時停止キーワードの追加を除いて、通常の機能の構文と似ています。パラメータを受け取り、戻り値の型を持つことができます。ただし、一時停止機能は、別の一時停止機能またはコルーチン内でのみ呼び出すことができます。

suspend fun backgroundTask(param: Int): Int {
     // long running operation
}

内部では、サスペンド関数はコンパイラーによってsuspendキーワードのない別の関数に変換されます。このキーワードはContinuation型の追加パラメーターを取ります。たとえば、上記の関数は、コンパイラによって次のように変換されます。

fun backgroundTask(param: Int, callback: Continuation<Int>): Int {
   // long running operation
}

Continuationは、関数が中断されている間にエラーが発生した場合、戻り値または例外を使用してコルーチンを再開するために呼び出される2つの関数を含むインターフェイスです。

interface Continuation<in T> {
   val context: CoroutineContext
   fun resume(value: T)
   fun resumeWithException(exception: Throwable)
}

コルーチンを中断することの正確な意味を理解するには、次のコードを実行することをお勧めします。

import kotlinx.coroutines.Dispatchers.Unconfined
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

var continuation: Continuation<Int>? = null

fun main() = runBlocking {
    launch(Unconfined) {
        val a = a()
        println("Result is $a")
    }
    10.downTo(0).forEach {
        continuation!!.resume(it)
    }
}

suspend fun a(): Int {
    return b()
}

suspend fun b(): Int {
    while (true) {
        val i = suspendCoroutine<Int> { cont -> continuation = cont }
        if (i == 0) {
            return 0
        }
    }
}

Unconfinedコルーチンディスパッチャは、コルーチンディスパッチの魔法を排除し、裸のコルーチンに直接集中できるようにします。

launchブロック内のコードは、launch呼び出しの一部として、現在のスレッドですぐに実行を開始します。起こることは次のとおりです。

  1. val a = a()を評価する
  2. これはb()にチェーンし、suspendCoroutineに到達します。
  3. 関数b()は、suspendCoroutineに渡されたブロックを実行し、特別なCOROUTINE_SUSPENDED値を返します。この値はKotlinプログラミングモデルでは確認できませんが、コンパイルされたJavaメソッドはそれを実行します。
  4. 関数a()は、この戻り値を見て、それ自体も返します。
  5. launchブロックも同じことを行い、launch呼び出しの後の行に制御が戻ります:10.downTo(0)...

この時点で、launchブロック内のコードとfun mainコードが同時に実行されている場合と同じ効果があることに注意してください。これはすべて、単一のネイティブスレッドで発生しているため、launchブロックは「中断」されています。

これで、forEachループコード内で、プログラムはb()関数が書き込んだcontinuation10の値を持つresumesを読み取ります。 resume()は、渡された値でsuspendCoroutine呼び出しが返されるように実装されます。そのため、突然b()を実行していることに気付くでしょう。 resume()に渡した値はiに割り当てられ、0に対してチェックされます。ゼロでない場合、while (true)ループはb()内で継続し、再びsuspendCoroutineに到達します。この時点でresume()呼び出しが戻り、今度はforEach()の別のループステップを実行します。これは、最後に0で再開するまで続き、その後printlnステートメントが実行され、プログラムが完了します。

上記の分析により、「コルーチンの一時停止」とは、コントロールを最も内側のlaunch呼び出し(または、より一般的にはコルーチンビルダー)に戻すことを意味するという重要な直観が得られます。再開後にコルーチンが再び中断されると、resume()呼び出しは終了し、resume()の呼び出し元に制御が戻ります。

コルーチンディスパッチャの存在は、ほとんどのコードがすぐに別のスレッドにコードを送信するため、この推論の明確性を低下させます。その場合、上記の話は他のスレッドで発生し、コルーチンディスパッチャはcontinuationオブジェクトも管理するため、戻り値が利用可能になったときに再開できます。

14
Marko Topolnik

コルーチンまたは機能が中断されますか?

サスペンドの呼び出しing function suspend sコルーチン。現在のスレッドが別のコルーチンの実行を開始できることを意味します。したがって、coroutineは、関数ではなく中断されていると言われます。

しかし、技術的には、その時点で関数は別のコルーチンによって実行されないため、関数とコルーチンの両方が停止すると言うことができますが、ここでは髪を分割しています。

どのコルーチンが中断されますか?

外側のasyncはコルーチンを開始します。 computation()を呼び出すと、内側のasyncは2番目のコルーチンを開始します。その後、await()の呼び出しは、outerasyncコルーチンの実行を、innerasyncのコルーチンは終了しました。

単一のスレッドでもそれを見ることができます。スレッドは外側のasyncの先頭を実行し、computation()を呼び出して内側のasyncに到達します。この時点で、内部非同期の本体はスキップされ、スレッドはawait()に達するまで外部asyncの実行を続けます。 awaitは一時停止機能であるため、await()は「一時停止ポイント」です。これは、外側のコルーチンが中断され、スレッドが内側のコルーチンの実行を開始することを意味します。完了すると、外側のasyncの終わりを実行するために戻ります。

一時停止とは、外部非同期コルーチンが内部計算コルーチンの終了を待機している間(待機)、外部非同期コルーチンがアイドル状態(一時停止)でスレッドプールにスレッドを返し、子計算コルーチンが終了したことを意味します、それ(外部非同期コルーチン)が起動し、プールから別のスレッドを取得して続行しますか?

はい、正確に。

5
Joffrey

suspendを理解する最善の方法は、thisキーワードとcoroutineContextプロパティを類推することです。

Kotlin関数は、ローカルまたはグローバルとして宣言できます。ローカル関数はthisキーワードに魔法のようにアクセスできますが、グローバル関数にはアクセスできません。

Kotlin関数は、suspendまたはブロッキングとして宣言できます。 suspend関数はcoroutineContextプロパティに魔法のようにアクセスできますが、ブロック関数にはアクセスできません。

事は:coroutineContext property 「通常の」プロパティのように宣言されている Kotlin stdlibであるが、この宣言はドキュメント/ナビゲーションの目的のためのスタブにすぎない。実際、coroutineContextビルトイン組み込みプロパティ です。これは、言語キーワードを認識しているように、コンパイラがこのプロパティをマジックで認識していることを意味します。

thisキーワードがローカル関数に対して行うことは、coroutineContextプロパティがsuspend関数に対して行うことです。これは、現在の実行コンテキストへのアクセスを提供します。 2番目のケース。

したがって、suspendプロパティへのアクセスを取得するには、coroutineContextが必要です-現在実行されているコルーチンコンテキストのインスタンス

継続の概念の簡単な例を挙げたいと思います。これは、サスペンド機能がフリーズ/サスペンドしてから続行/再開できることです。スレッドとセマフォの観点からコルーチンを考えるのをやめます。継続とコールバックフックの観点から考えてください。

明確にするために、疑わしい機能を使用して、コートを一時停止することができます。これを調査しましょう:

Androidでは、たとえば次のようにできます。

var TAG = "myTAG:"
        fun myMethod() {
            viewModelScope.launch(Dispatchers.Default) {
                for (i in 10..15) {
                    if (i == 10) { //on first iteration, we will completely FREEZE this coroutine (just for loop here gets 'suspended`)
                        println("$TAG im a tired coroutine - let someone else print the numbers async. i'll suspend until your done")
                        freezePleaseIAmDoingHeavyWork()
                    } else
                        println("$TAG $i")
                }
            }

            //this area is not suspended, you can continue doing work
        }


        suspend fun freezePleaseIAmDoingHeavyWork() {
            withContext(Dispatchers.Default) {
                async {
                    //pretend this is a big network call
                    for (i in 1..10) {
                        println("$TAG $i")
                        delay(1_000)//delay pauses coroutine, NOT the thread. use  Thread.sleep if you want to pause a thread. 
                    }
                    println("$TAG phwww finished printing those numbers async now im tired, thank you for freezing, you may resume")
                }
            }
        }

次を印刷します。

I: myTAG: my coroutine is frozen but i can carry on to do other things

I: myTAG: im a tired coroutine - let someone else print the numbers async. i'll suspend until your done

I: myTAG: 1
I: myTAG: 2
I: myTAG: 3
I: myTAG: 4
I: myTAG: 5
I: myTAG: 6
I: myTAG: 7
I: myTAG: 8
I: myTAG: 9
I: myTAG: 10

I: myTAG: phwww finished printing those numbers async now im tired, thank you for freezing, you may resume

I: myTAG: 11
I: myTAG: 12
I: myTAG: 13
I: myTAG: 14
I: myTAG: 15

このように機能すると想像してください:

enter image description here

そのため、起動した現在の機能は停止せず、コルーチンが継続している間だけ停止します。スレッドは、サスペンド機能を実行しても一時停止しません。

このサイトが役に立ちます あなたはまっすぐに物事を出し、私の参考になります。

イテレーションの途中で何かクールなことをして、サスペンド機能をフリーズさせます。後でonResumeで再開します。

継続と呼ばれる変数を保存し、コルーチンの継続オブジェクトを適切にロードします。

var continuation: CancellableContinuation<String>? = null

suspend fun freezeHere() = suspendCancellableCoroutine<String> {
            continuation = it
        }

 fun unFreeze(){
            continuation?.resume("im resuming") {}
        }

ここで、サスペンド関数に戻り、反復の途中でフリーズさせます。

 suspend fun freezePleaseIAmDoingHeavyWork() {
        withContext(Dispatchers.Default) {
            async {
                //pretend this is a big network call
                for (i in 1..10) {
                    println("$TAG $i")
                    delay(1_000)
                    if(i == 3)
                        freezeHere() //dead pause, do not go any further
                }

            }
        }
    }

次に、onResumeのような他の場所(たとえば):

override fun onResume() {
        super.onResume()
        unFreeze()
    }

ループが継続します。一時停止関数をいつでもフリーズして、しばらく経ってから再開できることを知っているのは、とてもすてきです。 channels を調べることもできます

1
j2emanue