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: