Kotlin-Koroutinen ermöglichen es Ihnen, sauberen, vereinfachten asynchronen Code zu schreiben, Deine App reagiert schnell und verwaltet lang andauernde Aufgaben wie Netzwerkaufrufe oder Laufwerkvorgänge.
In diesem Thema erhalten Sie eine detaillierte Beschreibung von gemeinsamen Routinen unter Android. Wenn Sie nicht mit Koroutinen vertraut sind, lesen Sie Kotlin-Coroutinen für Android lesen, bevor Sie dieses Thema lesen.
Lang andauernde Aufgaben verwalten
Koroutinen basieren auf regulären Funktionen und fügen zwei Operationen hinzu, lang andauernden Aufgaben. Zusätzlich zu invoke
(oder call
) und return
Koroutinen fügen suspend
und resume
hinzu:
suspend
pausiert die Ausführung der aktuellen Koroutine und speichert alle lokalen Variablen.resume
führt eine ausgesetzte Koroutine von diesem Ort aus fort wo es gesperrt wurde.
Sie können suspend
-Funktionen nur von anderen suspend
-Funktionen aus aufrufen oder indem Sie einen Koroutinen-Builder wie launch
verwenden, um eine neue Koroutine zu starten.
Das folgende Beispiel zeigt eine einfache Koroutinenimplementierung für eine hypothetische Aufgabe mit langer Ausführungszeit:
suspend fun fetchDocs() { // Dispatchers.Main val result = get("https://developer.android.com") // Dispatchers.IO for `get` show(result) // Dispatchers.Main } suspend fun get(url: String) = withContext(Dispatchers.IO) { /* ... */ }
In diesem Beispiel wird get()
weiterhin im Hauptthread ausgeführt, coroutine hinzu, bevor die Netzwerkanfrage gestartet wird. Wenn die Netzwerkanfrage abgeschlossen ist, setzt get
die angehaltene Koroutine fort, statt einen Callback zu verwenden. um den Hauptthread zu benachrichtigen.
In Kotlin wird ein Stapelframe verwendet, um zu verwalten, welche Funktion ausgeführt wird. mit beliebigen lokalen Variablen. Beim Aussetzen einer Koroutine wird der aktuelle Stapel wird kopiert und für später gespeichert. Beim Fortsetzen wird der Stapelframe wurden von der Stelle, an der sie gespeichert wurden, zurückgesetzt und die Funktion wird wieder ausgeführt. Auch wenn der Code wie eine gewöhnliche sequenzielle Blockierung -Anfrage stellt die Koroutine sicher, dass die Netzwerkanfrage im Hauptthread.
Koroutinen für die Hauptsicherheit verwenden
Kotlin-Koroutinen verwenden Disponenten, um zu bestimmen, welche Threads für Koroutinen. Wenn Sie Code außerhalb des Hauptthreads ausführen möchten, können Sie Kotlin mitteilen, Koroutinen, um Arbeiten auf dem Default- oder IO-Dispatcher auszuführen. In Für Kotlin müssen alle Koroutinen in einem Dispatcher ausgeführt werden, auch wenn sie auf im Hauptthread. Koroutinen können sich selbst aussetzen, während der Disponent für die Wiederaufnahme verantwortlich.
In Kotlin stehen drei Dispatcher zur Verfügung, um anzugeben, wo die Koroutinen ausgeführt werden sollen. die Sie verwenden können:
- Dispatchers.Main – Verwenden Sie diesen Dispatcher, um eine gemeinsame Routine auf dem Main auszuführen. Android-Thread. Dieser sollte nur für die Interaktion mit der Benutzeroberfläche und schnell Arbeit auszuführen. Beispiele hierfür sind das Aufrufen von
suspend
-Funktionen, das Ausführen Vorgänge im Android-UI-Framework und Aktualisieren vonLiveData
-Objekte. - Dispatchers.IO: Dieser Dispatcher ist für die Ausführung von Laufwerken oder Netzwerken optimiert. E/A außerhalb des Hauptthreads. Beispiele hierfür sind die Verwendung des Komponente „Raum“ Lesen oder Schreiben in Dateien und Ausführen von Netzwerkvorgängen.
- Dispatchers.Default – Dieser Dispatcher ist für seine Leistung optimiert. CPU-intensive Arbeit außerhalb des Hauptthreads Beispielanwendungsfälle beinhalten das Sortieren einer und Parsen von JSON.
Ausgehend vom vorherigen Beispiel können Sie die Disponenten dazu verwenden, get
. Rufen Sie im Text von get
withContext(Dispatchers.IO)
auf, um einen Block zu erstellen, der im E/A-Thread-Pool ausgeführt wird. Code, den Sie darin -Block wird immer über den IO
-Dispatcher ausgeführt. Da withContext
selbst ein Anhalten-Funktion ist die Funktion get
ebenfalls eine Anhalten-Funktion.
suspend fun fetchDocs() { // Dispatchers.Main val result = get("developer.android.com") // Dispatchers.Main show(result) // Dispatchers.Main } suspend fun get(url: String) = // Dispatchers.Main withContext(Dispatchers.IO) { // Dispatchers.IO (main-safety block) /* perform network IO here */ // Dispatchers.IO (main-safety block) } // Dispatchers.Main }
Mit Koroutinen können Sie Threads detailliert steuern. Weil Mit withContext()
können Sie den Threadpool jeder Codezeile steuern, ohne Callbacks eingeführt, können Sie diese auf sehr kleine Funktionen anwenden, oder durch eine Netzwerkanfrage. Es hat sich bewährt, withContext()
, um dafür zu sorgen, dass jede Funktion main-sicher ist. Das bedeutet, dass Sie die Funktion aus dem Hauptthread aufrufen kann. So muss der Anrufer überlegen Sie sich, welcher Thread zum Ausführen der Funktion verwendet werden soll.
Im vorherigen Beispiel wird fetchDocs()
im Hauptthread ausgeführt. Allerdings kann get
sicher aufrufen, wodurch im Hintergrund eine Netzwerkanfrage ausgeführt wird. Da Koroutinen suspend
und resume
unterstützen, wird die Koroutine auf der Hauptseite verwendet. Der Thread wird mit dem Ergebnis get
fortgesetzt, sobald der withContext
-Block fertig.
Leistung von withContext()
withContext()
keinen zusätzlichen Aufwand im Vergleich zu einem gleichwertigen Callback-basierten System mit sich bringt. Implementierung. Darüber hinaus ist es möglich, withContext()
-Aufrufe zu optimieren. über eine äquivalente Callback-basierte Implementierung hinaus. Für Beispiel: Wenn eine Funktion zehn Aufrufe an ein Netzwerk sendet, können Sie Kotlin mitteilen, tauschen Threads nur einmal mit einem äußeren withContext()
. Dann, obwohl verwendet die Netzwerkbibliothek withContext()
mehrmals, bleibt auf derselben und vermeidet den Wechsel von Threads. Außerdem optimiert Kotlin den Wechsel zwischen Dispatchers.Default
und Dispatchers.IO
, um Thread-Wechsel zu vermeiden wenn möglich.
Koroutine starten
Sie haben zwei Möglichkeiten, Koroutinen zu starten:
launch
startet eine neue Koroutine und gibt das Ergebnis nicht an den Aufrufer zurück. Beliebig die als „Feuer und Vergessen“ gilt kann mitlaunch
gestartet werden.async
startet eine neue Koroutine und ermöglicht Ihnen, ein Ergebnis mit einer Sperrung zurückzugeben mit dem Namenawait
.
Normalerweise sollten Sie mit launch
eine neue Koroutine aus einer regulären Funktion als reguläre Funktion await
nicht aufrufen. async
nur im Innenbereich verwenden einer anderen Koroutine oder einer Anhalten-Funktion parallele Zerlegung.
Parallelzerlegung
Alle Koroutinen, die in einer suspend
-Funktion gestartet werden, müssen beendet werden, wenn diese Funktion zurückgibt. Sie müssen also wahrscheinlich sicherstellen, dass diese Koroutinen bevor Sie zurückkehren. Mit strukturierten Nebenläufigkeit in Kotlin können Sie Eine coroutineScope
, die eine oder mehrere Koroutinen startet. Dann mit await()
(für eine einzelne Koroutine) oder awaitAll()
(für mehrere Koroutinen) können Sie dass diese Koroutinen vor der Rückkehr von der Funktion beendet werden.
Definieren wir als Beispiel ein coroutineScope
, das zwei Dokumente abruft asynchron programmiert. Durch den Aufruf von await()
für jede zurückgestellte Referenz garantieren wir, dass beide async
-Vorgänge abgeschlossen sind, bevor ein Wert zurückgegeben wird:
suspend fun fetchTwoDocs() = coroutineScope { val deferredOne = async { fetchDoc(1) } val deferredTwo = async { fetchDoc(2) } deferredOne.await() deferredTwo.await() }
Sie können awaitAll()
auch für Sammlungen verwenden, wie im folgenden Beispiel gezeigt:
suspend fun fetchTwoDocs() = // called on any Dispatcher (any thread, possibly Main) coroutineScope { val deferreds = listOf( // fetch two docs at the same time async { fetchDoc(1) }, // async returns a result for the first doc async { fetchDoc(2) } // async returns a result for the second doc ) deferreds.awaitAll() // use awaitAll to wait for both network requests }
Obwohl fetchTwoDocs()
neue Koroutinen mit async
startet, führt die Funktion verwendet awaitAll()
, um auf den Abschluss dieser gestarteten Koroutinen zu warten, bevor zurückkehrt. Hinweis: Auch wenn awaitAll()
nicht aufgerufen wurde, coroutineScope
-Builder setzt die Koroutine nicht fort, die aufgerufen wurde fetchTwoDocs
, bis alle neuen Koroutinen abgeschlossen sind.
Außerdem fängt coroutineScope
alle Ausnahmen ab, die von den Koroutinen ausgelöst werden und leitet sie an den Anrufer zurück.
Weitere Informationen zur parallelen Zerlegung findest du unter Aussetzende Funktionen erstellen
Konzepte von Koroutinen
KoroutineScope
CoroutineScope
verfolgt alle Koroutinen, die es mit launch
oder async
erzeugt. Die laufende Arbeiten (d.h. die laufenden Koroutinen) können durch Aufrufen von scope.cancel()
. In Android bieten einige KTX-Bibliotheken eigene CoroutineScope
für bestimmte Lebenszyklusklassen. Beispiel: ViewModel
hat einen viewModelScope
, und Lifecycle
hat lifecycleScope
. Im Gegensatz zu einem Dispatcher führt ein CoroutineScope
die Koroutinen jedoch nicht aus.
viewModelScope
wird auch in den Beispielen verwendet in Threads im Hintergrund unter Android mit Coroutines Wenn Sie jedoch Ihren eigenen CoroutineScope
erstellen müssen, um den Lebenszyklus von Koroutinen in einer bestimmten Ebene Ihrer App wie folgt:
class ExampleClass { // Job and Dispatcher are combined into a CoroutineContext which // will be discussed shortly val scope = CoroutineScope(Job() + Dispatchers.Main) fun exampleMethod() { // Starts a new coroutine within the scope scope.launch { // New coroutine that can call suspend functions fetchDocs() } } fun cleanUp() { // Cancel the scope to cancel ongoing coroutines work scope.cancel() } }
Ein abgebrochener Bereich kann keine weiteren Koroutinen erstellen. Daher sollten Sie scope.cancel()
nur dann aufrufen, wenn die Klasse, die ihren Lebenszyklus steuert, zerstört wird. Bei Verwendung von viewModelScope
gibt der Parameter ViewModel
-Klasse bricht den automatisch mit der Methode onCleared()
von ViewModel.
Job
Job
ist ein Handle zu einer Koroutine. Jede Koroutine, die Sie mit launch
erstellen, oder async
gibt eine Job
-Instanz zurück, die das Ereignis und verwaltet deren Lebenszyklus. Sie können auch einen Job
an einen CoroutineScope
, um den Lebenszyklus weiter zu verwalten, wie unten gezeigt. Beispiel:
class ExampleClass { ... fun exampleMethod() { // Handle to the coroutine, you can control its lifecycle val job = scope.launch { // New coroutine } if (...) { // Cancel the coroutine started above, this doesn't affect the scope // this coroutine was launched in job.cancel() } } }
CoroutineContext
CoroutineContext
definiert das Verhalten einer Koroutine mithilfe der folgenden Elemente:
Job
: Steuert den Lebenszyklus der Koroutine.CoroutineDispatcher
: Weiterleitungen arbeiten an den entsprechenden Thread.CoroutineName
: Der Name der Koroutine, der für die Fehlerbehebung hilfreich ist.CoroutineExceptionHandler
: Verarbeitet nicht abgefangene Ausnahmen.
Für neue Koroutinen, die innerhalb eines Bereichs erstellt wurden, wird eine neue Job
-Instanz erstellt. der neuen Koroutine zugewiesen ist, und die anderen CoroutineContext
-Elemente werden aus dem einschließenden Bereich übernommen. Sie können die übernommenen -Elementen durch Übergeben eines neuen CoroutineContext
an launch
oder async
. Die Übergabe von Job
an launch
oder async
hat keine Auswirkungen. als neue Instanz von Job
wird immer einer neuen Koroutine zugewiesen.
class ExampleClass { val scope = CoroutineScope(Job() + Dispatchers.Main) fun exampleMethod() { // Starts a new coroutine on Dispatchers.Main as it's the scope's default val job1 = scope.launch { // New coroutine with CoroutineName = "coroutine" (default) } // Starts a new coroutine on Dispatchers.Default val job2 = scope.launch(Dispatchers.Default + CoroutineName("BackgroundCoroutine")) { // New coroutine with CoroutineName = "BackgroundCoroutine" (overridden) } } }
Zusätzliche Ressourcen für Koroutinen
Weitere Ressourcen zu Koroutinen finden Sie unter den folgenden Links: