Was ist der Unterschied zwischen Start / Join und Async / Warten in Kotlin-Coroutinen?

156

In der kotlinx.coroutinesBibliothek können Sie eine neue Coroutine entweder mit launch(mit join) oder async(mit await) starten . Was ist der Unterschied zwischen ihnen?

Roman Elizarov
quelle

Antworten:

232
  • launchwird verwendet, um Coroutine zu feuern und zu vergessen . Es ist wie ein neuer Thread. Wenn der Code in der launchDatei mit Ausnahme beendet wird, wird er in einem Thread wie eine nicht erfasste Ausnahme behandelt - normalerweise in JVM-Backend-Anwendungen auf stderr gedruckt und stürzt Android-Anwendungen ab. joinwird verwendet, um auf den Abschluss der gestarteten Coroutine zu warten und ihre Ausnahme nicht weiterzugeben. Allerdings ist ein abgestürzt Kind bricht Koroutine seine Eltern mit der entsprechenden Ausnahme, auch.

  • asyncwird verwendet, um eine Coroutine zu starten, die ein Ergebnis berechnet . Das Ergebnis wird durch eine Instanz dargestellt Deferredund Sie müssen verwenden awaitdarauf. Eine nicht erfasste Ausnahme im asyncCode wird im Ergebnis gespeichert Deferredund nirgendwo anders geliefert. Sie wird stillschweigend gelöscht, sofern sie nicht verarbeitet wird. Sie dürfen die Coroutine, die Sie mit Async begonnen haben, NICHT vergessen .

Roman Elizarov
quelle
1
Ist Async der richtige Coroutine Builder für Netzwerkanrufe in Android?
Faraaz
Der richtige Coroutinenbauer hängt davon ab, was Sie erreichen wollen
Roman Elizarov
9
Können Sie näher auf "Sie dürfen die Coroutine, die Sie mit Async begonnen haben, NICHT vergessen" näher eingehen? Gibt es Fallstricke, die man zum Beispiel nicht erwarten würde?
Luis
2
"Eine nicht erfasste Ausnahme im asynchronen Code wird im resultierenden verzögerten Code gespeichert und nirgendwo anders zugestellt. Sie wird stillschweigend gelöscht, sofern sie nicht verarbeitet wird."
Roman Elizarov
9
Wenn Sie das Ergebnis von Async vergessen, wird es beendet und es wird Müll gesammelt. Wenn es jedoch aufgrund eines Fehlers in Ihrem Code abstürzt, werden Sie nie davon erfahren. Deswegen.
Roman Elizarov
77

Ich finde diesen Leitfaden https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md nützlich. Ich werde die wesentlichen Teile zitieren

🦄 Coroutine

Coroutinen sind im Wesentlichen leichte Fäden.

Sie können sich Coroutine also als etwas vorstellen, das Threads auf sehr effiziente Weise verwaltet.

🐤 starten

fun main(args: Array<String>) {
    launch { // launch new coroutine in background and continue
        delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
        println("World!") // print after delay
    }
    println("Hello,") // main thread continues while coroutine is delayed
    Thread.sleep(2000L) // block main thread for 2 seconds to keep JVM alive
}

So launchstartet einen Hintergrund - Thread, etwas tut, und gibt eine sofort als Token Job. Sie können dies aufrufen, joinum Jobzu blockieren, bis dieser launchThread abgeschlossen ist

fun main(args: Array<String>) = runBlocking<Unit> {
    val job = launch { // launch new coroutine and keep a reference to its Job
        delay(1000L)
        println("World!")
    }
    println("Hello,")
    job.join() // wait until child coroutine completes
}

🦆 async

Konzeptionell ist Async wie ein Start. Es wird eine separate Coroutine gestartet, bei der es sich um einen leichten Faden handelt, der gleichzeitig mit allen anderen Coroutinen arbeitet. Der Unterschied besteht darin, dass der Start einen Job zurückgibt und keinen resultierenden Wert enthält, während Async einen verzögerten Wert zurückgibt - eine leichte, nicht blockierende Zukunft, die das Versprechen darstellt, später ein Ergebnis bereitzustellen.

So asyncstartet einen Hintergrund - Thread, etwas tut, und gibt eine sofort als Token Deferred.

fun main(args: Array<String>) = runBlocking<Unit> {
    val time = measureTimeMillis {
        val one = async { doSomethingUsefulOne() }
        val two = async { doSomethingUsefulTwo() }
        println("The answer is ${one.await() + two.await()}")
    }
    println("Completed in $time ms")
}

Sie können .await () für einen zurückgestellten Wert verwenden, um das endgültige Ergebnis zu erhalten. Zurückgestellt ist jedoch auch ein Job, sodass Sie ihn bei Bedarf abbrechen können.

Also Deferredist eigentlich ein Job. Siehe https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-deferred/index.html

interface Deferred<out T> : Job (source)

🦋 Async ist standardmäßig eifrig

Es gibt eine Faulheitsoption zum Asynchronisieren mit einem optionalen Startparameter mit dem Wert CoroutineStart.LAZY. Die Coroutine wird nur gestartet, wenn das Ergebnis von einigen Wartenden benötigt wird oder wenn eine Startfunktion aufgerufen wird.

onmyway133
quelle
12

launchund asyncwerden verwendet, um neue Coroutinen zu starten. Sie führen sie jedoch auf unterschiedliche Weise aus.

Ich möchte ein sehr einfaches Beispiel zeigen, das Ihnen hilft, Unterschiede sehr leicht zu verstehen

  1. starten
    class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        btnCount.setOnClickListener {
            pgBar.visibility = View.VISIBLE
            CoroutineScope(Dispatchers.Main).launch {
                val currentMillis = System.currentTimeMillis()
                val retVal1 = downloadTask1()
                val retVal2 = downloadTask2()
                val retVal3 = downloadTask3()
                Toast.makeText(applicationContext, "All tasks downloaded! ${retVal1}, ${retVal2}, ${retVal3} in ${(System.currentTimeMillis() - currentMillis)/1000} seconds", Toast.LENGTH_LONG).show();
                pgBar.visibility = View.GONE
            }
        }

    // Task 1 will take 5 seconds to complete download
    private suspend fun downloadTask1() : String {
        kotlinx.coroutines.delay(5000);
        return "Complete";
    }

    // Task 1 will take 8 seconds to complete download    
    private suspend fun downloadTask2() : Int {
        kotlinx.coroutines.delay(8000);
        return 100;
    }

    // Task 1 will take 5 seconds to complete download
    private suspend fun downloadTask3() : Float {
        kotlinx.coroutines.delay(5000);
        return 4.0f;
    }
}

In diesem Beispiel lädt mein Code 3 Daten per btnCountKnopfdruck pgBarherunter und zeigt den Fortschrittsbalken an, bis der gesamte Download abgeschlossen ist. Es gibt 3 suspendFunktionen downloadTask1(), downloadTask2()und downloadTask3()welche die Download - Daten. Um es zu simulieren, habe ich delay()in diesen Funktionen verwendet. Diese Funktionen warten 5 seconds, 8 secondsund 5 secondsjeweils.

Da wir launchdiese Suspend-Funktionen zum Starten verwendet haben, launchwerden sie nacheinander (einzeln) ausgeführt . Dies bedeutet, dass downloadTask2()der Start nach downloadTask1()Abschluss und downloadTask3()erst nach downloadTask2()Abschluss beginnen würde.

Wie im Ausgabe-Screenshot würde die ToastGesamtausführungszeit zum Abschließen aller 3 Downloads zu 5 Sekunden + 8 Sekunden + 5 Sekunden = 18 Sekunden mit führenlaunch

Beispiel starten

  1. asynchron

Wie wir gesehen haben, launchist die Ausführung sequentiallyfür alle 3 Aufgaben möglich. Die Zeit, um alle Aufgaben zu erledigen, war 18 seconds.

Wenn diese Aufgaben unabhängig sind und das Berechnungsergebnis anderer Aufgaben nicht benötigen, können sie ausgeführt werden concurrently. Sie würden zur gleichen Zeit starten und gleichzeitig im Hintergrund laufen. Dies kann mit gemacht werden async.

asyncGibt eine Instanz vom Deffered<T>Typ zurück, wobei Tes sich um den Datentyp handelt, den unsere Suspend-Funktion zurückgibt. Beispielsweise,

  • downloadTask1()würde zurückgeben, Deferred<String>da String der Rückgabetyp der Funktion ist
  • downloadTask2()würde zurückkehren, Deferred<Int>da Int der Rückgabetyp der Funktion ist
  • downloadTask3()würde zurückkehren, Deferred<Float>da Float der Funktionstyp return ist

Wir können das Rückgabeobjekt vom asyncTyp verwenden Deferred<T>, um den zurückgegebenen Wert vom Typ Tabzurufen. Das kann mit await()Anruf gemacht werden. Überprüfen Sie zum Beispiel den folgenden Code

        btnCount.setOnClickListener {
        pgBar.visibility = View.VISIBLE

        CoroutineScope(Dispatchers.Main).launch {
            val currentMillis = System.currentTimeMillis()
            val retVal1 = async(Dispatchers.IO) { downloadTask1() }
            val retVal2 = async(Dispatchers.IO) { downloadTask2() }
            val retVal3 = async(Dispatchers.IO) { downloadTask3() }

            Toast.makeText(applicationContext, "All tasks downloaded! ${retVal1.await()}, ${retVal2.await()}, ${retVal3.await()} in ${(System.currentTimeMillis() - currentMillis)/1000} seconds", Toast.LENGTH_LONG).show();
            pgBar.visibility = View.GONE
        }

Auf diese Weise haben wir alle drei Aufgaben gleichzeitig gestartet. Meine gesamte Ausführungszeit wäre also nur 8 secondsdie Zeit, für downloadTask2()die es die größte aller drei Aufgaben ist. Sie können dies im folgenden Screenshot in sehenToast message

warte auf Beispiel

Kushal
quelle
1
Vielen Dank für die Erwähnung, dass dies launchfür sequentielle Spaß ist, während asyncfür gleichzeitige
Akbolat SSS
Sie haben start einmal für alle Aufgaben und asynchron für jede Aufgabe verwendet. Vielleicht ist es schneller, weil jeder in einer anderen Coroutine gestartet wurde und nicht auf jemanden wartet? Dies ist ein falscher Vergleich. Normalerweise ist die Leistung gleich. Ein wesentlicher Unterschied besteht darin, dass beim Start immer eine neue Coroutine anstelle einer asynchronen Coroutine gestartet wird, die den Eigentümer aufteilt. Ein weiterer Faktor ist, dass die übergeordnete Coroutine ebenfalls fehlschlägt, wenn eine der asynchronen Aufgaben aus einem bestimmten Grund fehlschlägt. Deshalb ist Async nicht so beliebt wie Launch.
p2lem8dev
1
Diese Antwort ist nicht richtig, da Async mit Suspend-Funktionen direkt verglichen wird, anstatt zu starten. Anstatt die Suspend-Funktion direkt in einem Beispiel aufzurufen, werden Sie beim Aufrufen von launch (Dispatchers.IO) {downloadTask1 ()} feststellen, dass beide gleichzeitig und nicht nacheinander ausgeführt werden . Sie können keine Ausgaben abrufen, aber Sie werden feststellen, dass dies der Fall ist nicht sequentiell. Auch wenn Sie deferred.await () nicht verketten und deferred.await () separat aufrufen, werden Sie feststellen, dass Async sequentiell ist.
Thracian
2
-1 das ist einfach falsch. Beides launchund asyncwird neue Coroutinen starten. Sie vergleichen eine einzelne Coroutine ohne Kinder mit einer einzelnen Coroutine mit 3 Kindern. Sie könnten jeden der asyncAufrufe durch ersetzen, launchund in Bezug auf die Parallelität würde sich absolut nichts ändern.
Moira
Das Nebengeräusch in dieser Antwort erhöht die Komplexität, die außerhalb des Co-Routine-Themas liegt.
Truthadjustr
6
  1. Beide Coroutine-Builder, nämlich launch und async, sind im Grunde genommen Lambdas mit einem Empfänger vom Typ CoroutineScope, was bedeutet, dass ihr innerer Block als Suspend-Funktion kompiliert wird. Daher werden beide in einem asynchronen Modus ausgeführt UND beide führen ihren Block nacheinander aus.

  2. Der Unterschied zwischen Start und Async besteht darin, dass sie zwei verschiedene Möglichkeiten ermöglichen. Der Launch Builder gibt einen Job zurück, die asynchrone Funktion gibt jedoch ein zurückgestelltes Objekt zurück. Sie können launch verwenden, um einen Block auszuführen, von dem Sie keinen zurückgegebenen Wert erwarten, dh in eine Datenbank schreiben oder eine Datei speichern oder etwas verarbeiten, das im Grunde nur als Nebeneffekt bezeichnet wird. Auf der anderen Seite gibt Async, das ein Zurückgestelltes zurückgibt, wie ich zuvor angegeben habe, einen nützlichen Wert aus der Ausführung seines Blocks zurück, ein Objekt, das Ihre Daten umschließt, sodass Sie es hauptsächlich für das Ergebnis, möglicherweise aber auch für die Nebenwirkung verwenden können. NB: Sie können den verzögerten Wert entfernen und seinen Wert mit der Funktion wait abrufen, die die Ausführung Ihrer Anweisungen blockiert, bis ein Wert zurückgegeben oder eine Ausnahme ausgelöst wird!

  3. Beide Coroutine Builder (Start und Async) können abgebrochen werden.

  4. Noch etwas?: Ja, wenn beim Start eine Ausnahme innerhalb ihres Blocks ausgelöst wird, wird die Coroutine automatisch abgebrochen und die Ausnahmen werden zugestellt. Wenn dies andererseits mit Async geschieht, wird die Ausnahme nicht weiter verbreitet und sollte innerhalb des zurückgegebenen zurückgestellten Objekts abgefangen / behandelt werden.

  5. Weitere Informationen zu Coroutinen finden Sie unter https://kotlinlang.org/docs/tutorials/coroutines/coroutines-basic-jvm.html https://www.codementor.io/blog/kotlin-coroutines-6n53p8cbn1

AouledIssa
quelle
1
Danke für diesen Kommentar. Es wurden alle Punkte des Threads gesammelt. Ich möchte hinzufügen, dass nicht alle Starts abgebrochen werden, z. B. kann Atomic niemals abgebrochen werden.
p2lem8dev
4

Start gibt einen Job zurück

async gibt ein Ergebnis zurück (verzögerter Job)

Beim Starten mit Join wird gewartet, bis der Job abgeschlossen ist. Der Coroutine-Aufruf join () wird einfach angehalten, sodass der aktuelle Thread in der Zwischenzeit andere Arbeiten ausführen kann (z. B. das Ausführen einer anderen Coroutine).

Async wird verwendet, um einige Ergebnisse zu berechnen. Es erstellt eine Coroutine und gibt das zukünftige Ergebnis als Implementierung von Deferred zurück. Die laufende Coroutine wird abgebrochen, wenn die resultierende Verzögerung abgebrochen wird.

Stellen Sie sich eine asynchrone Methode vor, die einen Zeichenfolgenwert zurückgibt. Wenn die asynchrone Methode ohne Warten verwendet wird, wird eine verzögerte Zeichenfolge zurückgegeben. Wenn jedoch Warten verwendet wird, erhalten Sie als Ergebnis eine Zeichenfolge

Der Hauptunterschied zwischen Async und Start. Deferred gibt einen bestimmten Wert vom Typ T zurück, nachdem Ihre Coroutine die Ausführung abgeschlossen hat, Job hingegen nicht.

Spanne
quelle
0

Async vs Launch Async vs Launch Diff Image

Start / Async kein Ergebnis

  • Verwenden Sie, wenn Sie kein Ergebnis benötigen.
  • Blockieren Sie nicht den Code, in dem er aufgerufen wird.
  • Parallel laufen

asynchron für Ergebnis

  • Wenn Sie das Ergebnis abwarten müssen und aus Effizienzgründen parallel laufen können
  • Blockieren Sie den Code, in dem er aufgerufen wird
  • parallel laufen
Vinod Kamble
quelle