Ich habe Kotlin-Dokumente gelesen und wenn ich sie richtig verstanden habe, funktionieren die beiden Kotlin-Funktionen wie folgt:
withContext(context)
: wechselt den Kontext der aktuellen Coroutine, wenn der angegebene Block ausgeführt wird, wechselt die Coroutine zurück zum vorherigen Kontext.async(context)
: Startet eine neue Coroutine im angegebenen Kontext. Wenn wir.await()
die zurückgegebeneDeferred
Task aufrufen, wird die aufrufende Coroutine angehalten und fortgesetzt, wenn der Block, der in der erzeugten Coroutine ausgeführt wird, zurückkehrt.
Nun zu den folgenden zwei Versionen von code
:
Version 1:
launch(){
block1()
val returned = async(context){
block2()
}.await()
block3()
}
Version 2:
launch(){
block1()
val returned = withContext(context){
block2()
}
block3()
}
- In beiden Versionen wird block1 (), block3 () im Standardkontext (commonpool?) Ausgeführt, während block2 () im angegebenen Kontext ausgeführt wird.
- Die Gesamtausführung ist synchron mit der Reihenfolge block1 () -> block2 () -> block3 ().
- Der einzige Unterschied, den ich sehe, ist, dass Version 1 eine andere Coroutine erstellt, wobei Version 2 nur eine Coroutine ausführt, während der Kontext gewechselt wird.
Meine Fragen sind:
Ist es nicht immer besser zu verwenden,
withContext
alsasync-await
weil es funktional ähnlich ist, aber keine andere Coroutine erstellt. Eine große Anzahl von Coroutinen ist zwar leicht, kann jedoch bei anspruchsvollen Anwendungen immer noch ein Problem darstellen.Gibt es einen Fall
async-await
, der vorzuziehen istwithContext
?
Update:
Kotlin 1.2.50 verfügt jetzt über eine Codeüberprüfung, in die konvertiert werden kann async(ctx) { }.await() to withContext(ctx) { }
.
kotlin
kotlin-coroutines
Mangat Rai Modi
quelle
quelle
withContext
, wird immer eine neue Coroutine erstellt, unabhängig davon. Das kann ich aus dem Quellcode ersehen.async/await
laut OP nicht auch eine neue Coroutine?Antworten:
Ich möchte diesen Mythos von "zu vielen Coroutinen" als Problem zerstreuen, indem ich ihre tatsächlichen Kosten quantifiziere.
Zunächst sollten wir die Coroutine selbst von dem Coroutine-Kontext trennen, an den sie gebunden ist. So erstellen Sie nur eine Coroutine mit minimalem Overhead:
Der Wert dieses Ausdrucks ist a
Job
Halten einer suspendierten Coroutine. Um die Fortsetzung beizubehalten, haben wir sie einer Liste im weiteren Bereich hinzugefügt.Ich habe diesen Code verglichen und festgestellt, dass er 140 Bytes zuweist und 100 Nanosekunden dauert . So leicht ist eine Coroutine.
Für die Reproduzierbarkeit ist dies der Code, den ich verwendet habe:
Dieser Code startet eine Reihe von Coroutinen und schläft dann, sodass Sie Zeit haben, den Heap mit einem Überwachungstool wie VisualVM zu analysieren. Ich habe die spezialisierten Klassen erstellt
JobList
undContinuationList
weil dies die Analyse des Heap-Dumps erleichtert.Um eine vollständigere Geschichte zu erhalten, habe ich den folgenden Code verwendet, um auch die Kosten von
withContext()
und zu messenasync-await
:Dies ist die typische Ausgabe, die ich aus dem obigen Code erhalte:
Ja,
async-await
dauert ungefähr doppelt so langewithContext
, aber es ist immer noch nur eine Mikrosekunde. Sie müssten sie in einer engen Schleife starten und fast nichts anderes tun, damit dies zu einem "Problem" in Ihrer App wird.Mit habe
measureMemory()
ich folgende Speicherkosten pro Anruf ermittelt:Die Kosten für
async-await
sind genau 140 Bytes höher alswithContext
die Zahl, die wir als Speichergewicht einer Coroutine erhalten haben. Dies ist nur ein Bruchteil der Gesamtkosten für die Einrichtung desCommonPool
Kontexts.Wenn die Auswirkung auf Leistung / Speicher das einzige Kriterium wäre, um zwischen
withContext
und zu entscheidenasync-await
, müsste die Schlussfolgerung sein, dass es in 99% der realen Anwendungsfälle keinen relevanten Unterschied zwischen ihnen gibt.Der wahre Grund ist, dass
withContext()
eine einfachere und direktere API, insbesondere im Hinblick auf die Ausnahmebehandlung:async { ... }
dass der übergeordnete Job abgebrochen wird. Dies geschieht unabhängig davon, wie Sie mit Ausnahmen vom Abgleich umgehenawait()
. Wenn Sie noch keine vorbereitetcoroutineScope
haben, wird möglicherweise Ihre gesamte Anwendung heruntergefahren.withContext { ... }
einfach durch denwithContext
Aufruf ausgelöst . Sie behandeln sie wie jede andere.withContext
wird auch optimiert, indem die Tatsache genutzt wird, dass Sie die Coroutine der Eltern aussetzen und auf das Kind warten, aber das ist nur ein zusätzlicher Bonus.async-await
sollte für die Fälle reserviert werden, in denen Sie tatsächlich Parallelität wünschen, damit Sie mehrere Coroutinen im Hintergrund starten und erst dann darauf warten. Zusamenfassend:async-await-async-await
- Tu das nicht, benutzewithContext-withContext
async-async-await-await
- so kann man es benutzen.quelle
async-await
: Wenn wir verwendenwithContext
, wird auch eine neue Coroutine erstellt (soweit ich aus dem Quellcode ersehen kann). Glauben Sie also, dass der Unterschied möglicherweise von einem anderen Ort stammt?async
erstellt einDeferred
Objekt, das möglicherweise auch den Unterschied erklärt.Thread.destroy()
- die Ausführung verschwindet in Luft.Sie sollten async / await verwenden, wenn Sie mehrere Aufgaben gleichzeitig ausführen möchten, zum Beispiel:
Wenn Sie nicht mehrere Aufgaben gleichzeitig ausführen müssen, können Sie withContext verwenden.
quelle
Denken Sie im Zweifelsfall wie an eine Faustregel:
Wenn mehrere Aufgaben parallel ausgeführt werden müssen und das Endergebnis von der Fertigstellung aller Aufgaben abhängt, verwenden Sie
async
.Verwenden Sie zum Zurückgeben des Ergebnisses einer einzelnen Aufgabe
withContext
.quelle
async
und daswithContext
Blockieren in einem Suspend-Bereich?async
undwithContext
den Hauptthread nicht blockieren, wird der Körper der Coroutine nur angehalten, während eine lange laufende Aufgabe ausgeführt wird und auf ein Ergebnis wartet. Weitere Informationen und Beispiele finden Sie in diesem Artikel zu Medium: Async Operations mit Kotlin Coroutines .