it-swarm.com.de

Was bedeutet die Suspend-Funktion in Kotlin Coroutine?

Ich lese Kotlin Coroutine und weiß, dass es auf der suspend-Funktion basiert. Was bedeutet suspend

Coroutine oder Funktion wird ausgesetzt?

Von https://kotlinlang.org/docs/reference/coroutines.html

Coroutinen sind im Grunde Berechnungen, die angehalten werden können, ohne einen Thread zu blockieren

Ich habe gehört, dass die Leute oft "Suspend-Funktion" sagen. Aber ich denke, es ist die Coroutine, die suspendiert wird, weil sie darauf wartet, dass die Funktion beendet ist? "Suspendieren" bedeutet normalerweise "Unterbrechen", in diesem Fall ist die Coroutine im Leerlauf.

???? Sollten wir sagen, dass die Coroutine suspendiert ist?

Welche Coroutine wird suspendiert?

Von https://kotlinlang.org/docs/reference/coroutines.html

Um die Analogie fortzusetzen, kann await () eine Suspendierungsfunktion sein (daher auch innerhalb eines asynchronen {} -Befehls aufgerufen werden), die eine Coroutine unterbricht, bis einige Berechnungen durchgeführt wurden und das Ergebnis zurückgibt:

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

???? Es besagt "dass eine Coroutine ausgesetzt wird, bis einige Berechnungen durchgeführt werden", aber Coroutine ist wie ein leichter Thread. Wenn also die Coroutine ausgesetzt ist, wie kann die Berechnung durchgeführt werden?

Wir sehen, dass await für computation aufgerufen wird, daher könnte asyncDeferred zurückgeben, was bedeutet, dass eine andere Coroutine gestartet werden kann

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

???? Das Zitat lautet das eine Coroutine aufhält. Bedeutet es suspend die äußere async Coroutine oder suspend die innere computation Coroutine?

Bedeutet suspend, dass, während die äußere async-Coroutine auf den Abschluss der inneren await-Coroutine wartet (computation) wartet, diese (die äußere async-Coroutine) im Leerlauf ist (daher der Name suspend) und den Thread an den Thread-Pool zurückgibt, und wenn die untergeordnete computation-Coroutine beendet ist , es (die äußere async coroutine) wacht auf, nimmt einen anderen Thread aus dem Pool und fährt fort?

Der Grund, warum ich den Thread erwähne, ist wegen https://kotlinlang.org/docs/tutorials/coroutines-basic-jvm.html

Der Thread wird an den Pool zurückgegeben, während die Coroutine wartet. Wenn das Warten abgeschlossen ist, wird die Coroutine mit einem freien Thread im Pool fortgesetzt

25
onmyway133

Anhalten von Funktionen stehen im Mittelpunkt von allem, was Coroutines . Eine Suspendierungsfunktion ist einfach eine Funktion, die zu einem späteren Zeitpunkt angehalten und wieder aufgenommen werden kann. Sie können eine lange laufende Operation ausführen und warten, bis sie ohne Blockierung abgeschlossen ist.

Die Syntax einer Suspendierungsfunktion ähnelt der einer regulären Funktion, mit der Ausnahme, dass das Suspend-Schlüsselwort hinzugefügt wird. Es kann einen Parameter annehmen und einen Rückgabetyp haben. Suspendierungsfunktionen können jedoch nur von einer anderen Suspendierungsfunktion oder innerhalb einer Coroutine aufgerufen werden.

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

Unter der Haube werden Suspend-Funktionen vom Compiler in eine andere Funktion ohne das Suspend-Schlüsselwort konvertiert, das einen zusätzlichen Parameter vom Typ Continuation übernimmt. Die obige Funktion wird beispielsweise vom Compiler folgendermaßen konvertiert:

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

Continuation ist eine Schnittstelle, die zwei Funktionen enthält, die aufgerufen werden, um die Coroutine mit einem Rückgabewert oder mit einer Ausnahme fortzusetzen, wenn beim Unterbrechen der Funktion ein Fehler aufgetreten ist.

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

Als Lernwerkzeug empfehle ich Ihnen, diesen Code durchzugehen, der den grundlegenden Mechanismus aufzeigt, der allen Komfortkonstrukten wie async zugrunde liegt:

import kotlinx.coroutines.experimental.Unconfined
import kotlinx.coroutines.experimental.launch
import kotlin.coroutines.experimental.Continuation
import kotlin.coroutines.experimental.suspendCoroutine

var continuation: Continuation<Int>? = null

fun main(args: Array<String>) {
    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
        }
    }
}

Der Unconfined coroutine-Dispatcher eliminiert im Grunde die Magie des Coroutine-Dispatchings: Der Code im launch-Block wird einfach als Teil des launch-Aufrufs ausgeführt. Was passiert, ist wie folgt:

  1. val a = a() bewerten
  2. Dies verkettet sich zu b() und erreicht suspendCoroutine.
  3. Die Funktion b() führt den an suspendCoroutine übergebenen Block aus und gibt dann einen speziellen COROUTINE_SUSPENDED-Wert zurück. Dieser Wert kann mit dem Kotlin-Programmiermodell nicht beobachtet werden. Dies ist jedoch die Aufgabe der kompilierten Java-Methode.
  4. Die Funktion a(), die diesen Rückgabewert sieht, gibt ihn selbst ebenfalls zurück.
  5. Der launch-Block führt dasselbe aus und die Steuerung kehrt jetzt nach dem Aufruf von launch zur Zeile zurück: 10.downTo(0)...

An dieser Stelle haben Sie den gleichen Effekt, als würden der Code im launch-Block und der fun main-Code gleichzeitig ausgeführt. Es passiert einfach, dass dies alles in einem einzigen nativen Thread geschieht, so dass der launch-Block "ausgesetzt" ist.

Innerhalb des forEach-Schleifencodes liest das Programm nun die continuation, die die b()-Funktion geschrieben hat, und resumes mit dem Wert von 10. resume() ist so implementiert, dass es so ist, als ob der Aufruf von suspendCoroutine mit dem von Ihnen übergebenen Wert zurückgegeben würde. So befinden Sie sich plötzlich mitten in der Ausführung von b(). Der an resume() übergebene Wert wird i zugewiesen und mit 0 abgeglichen. Wenn es nicht Null ist, wird die while (true)-Schleife innerhalb von b() fortgesetzt und erreicht erneut suspendCoroutine. Dann kehrt Ihr resume()-Aufruf zurück, und jetzt durchlaufen Sie einen weiteren Schleifenschritt in forEach(). Dies dauert an, bis Sie schließlich mit 0 fortfahren, die println-Anweisung wird ausgeführt und das Programm wird abgeschlossen.

Die obige Analyse sollte Ihnen die wichtige Intuition vermitteln, dass "Suspendieren einer Coroutine" bedeutet, das Steuerelement zum innersten Aufruf von launch (oder allgemeiner zu coroutine builder ) zurückzukehren. Wenn eine Coroutine nach dem Wiederaufnehmen wieder ausgesetzt wird, wird der Aufruf resume() beendet und die Steuerung kehrt zum Aufrufer von resume() zurück.

Die Anwesenheit eines Coroutine-Dispatchers macht diese Argumentation weniger eindeutig, da die meisten von ihnen Ihren Code sofort an einen anderen Thread senden. In diesem Fall geschieht die obige Geschichte in diesem anderen Thread und der Coroutine-Dispatcher verwaltet auch das continuation-Objekt, damit es es wieder aufnehmen kann, wenn der Rückgabewert verfügbar ist.

7
Marko Topolnik

Coroutine oder Funktion wird ausgesetzt?

Aufrufen eines Suspend ing function suspend s der Coroutine, dh der aktuelle Thread kann mit der Ausführung einer anderen Coroutine beginnen. Die coroutine wird also eher als die Funktion angehalten.

Technisch gesehen wird Ihre Funktion an diesem Punkt jedoch nicht von einer anderen Coroutine ausgeführt. Wir können also sagen, dass sowohl die Funktion als auch die Coroutine aufhören, aber wir teilen hier Haare.

Welche Coroutine wird suspendiert?

Die äußere async startet eine Coroutine. Wenn es computation() aufruft, startet die innere async eine zweite Coroutine. Dann ruft der Aufruf von await() die Ausführung der outerasync-Coroutine auf, bis die Ausführung der innerasync-Coroutine beendet ist.

Sie können das sogar mit einem einzelnen Thread sehen: Der Thread führt den äußeren Anfang der async aus, ruft dann computation() auf und erreicht die innere async. Zu diesem Zeitpunkt wird der Rumpf des inneren Async übersprungen, und der Thread führt das äußere async so lange aus, bis await().await() einen "Aufhängepunkt" erreicht, da await eine Suspendierungsfunktion ist Die äußere Coroutine ist aufgehängt, und so beginnt der Thread, die innere Coroutine auszuführen. Wenn es fertig ist, wird das Ende des äußeren async ausgeführt.

Bedeutet Suspend, dass während die äußere asynchrone Coroutine auf den Abschluss der inneren Rechenkoroutine wartet (warten), sich (die äußere asynchrone Coroutine) im Leerlauf befindet (daher der Name suspend) und den Thread an den Thread-Pool zurückgibt und wenn die untergeordnete Rechenkoroutine beendet ist , wacht er (die äußere asynchrone Coroutine) auf, nimmt einen weiteren Thread aus dem Pool und fährt fort?

Ja genau.

2
Joffrey

Ich habe festgestellt, dass der beste Weg, um suspend zu verstehen, darin besteht, eine Analogie zwischen dem this-Schlüsselwort und der coroutineContext -Eigenschaft zu erstellen.

Kotlin-Funktionen können als lokal oder global deklariert werden. Lokale Funktionen haben auf magische Weise Zugriff auf das Schlüsselwort this, globale dagegen nicht.

Kotlin-Funktionen können als suspend oder als blockierend deklariert werden. suspend Funktionen haben auf magische Weise Zugriff auf die Eigenschaft coroutineContext, während Funktionen nicht blockiert werden.

Die Sache ist: coroutineContext property wird wie eine "normale" Eigenschaft deklariert in Kotlin stdlib, aber diese Deklaration ist nur ein Stub für Dokumentations-/Navigationszwecke. Tatsächlich ist coroutineContexteingebaute intrinsische Eigenschaft , was bedeutet, dass unter der Haube des Compilers die Magie diese Eigenschaft wie die Kenntnis von Sprachschlüsselwörtern kennt.

Das Schlüsselwort this für lokale Funktionen hat dieselbe Funktion wie die Eigenschaft coroutineContext für suspend: Es gibt Zugriff auf den aktuellen Ausführungskontext - im ersten Fall auf den Kontext der Klasseninstanz und auf die Coroutine Instanzkontext im zweiten Fall.

Sie benötigen also suspend, um Zugriff auf die Eigenschaft coroutineContext zu erhalten - die Instanz des aktuell ausgeführten Coroutine-Kontexts

Ich wollte Ihnen ein einfaches Beispiel für das Konzept der Fortsetzung geben. Dies ist, was eine Suspend-Funktion tut, die einfrieren/suspendieren und dann fortsetzen/fortsetzen kann. Hören Sie auf, an Coroutine in Bezug auf Threads und Semaphore zu denken. Denken Sie an Fortsetzung und sogar Rückruf-Hooks.

Um klar zu sein, kann eine Courtine mit einer Verdächtigen-Funktion angehalten werden. Lass uns das untersuchen:

in Android könnten wir dies zum Beispiel tun:

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")
                }
            }
        }

die druckt die folgenden:

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

stell es dir so vor:

enter image description here

damit die aktuelle Funktion, von der aus Sie gestartet haben, nicht beendet wird, wird nur eine Coroutine angehalten, während sie fortgesetzt wird. Der Thread wird nicht durch Ausführen einer Suspend-Funktion angehalten.

ich denke diese Seite kann helfen Sie gerade Dinge und ist meine Referenz.

lassen Sie uns etwas Cooles tun und unsere Suspend-Funktion mitten in einer Iteration einfrieren. wir werden es später in fortsetzen:

speichere eine Variable namens continuation und lade sie mit dem coroutines continuation Objekt für uns:

var continuation: CancellableContinuation<String>? = null

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

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

kehren wir nun zu unserer Suspend-Funktion zurück und lassen sie während der Iteration einfrieren:

 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
                }

            }
        }
    }

dann irgendwo anders wie in onResume (zum Beispiel):

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

und die Schleife wird fortgesetzt. Schön zu wissen, dass wir eine Suspend-Funktion jederzeit einfrieren und nach einiger Zeit wiederaufnehmen können. Sie können auch nachsehen in Kanäle

1
j2emanue