CoroutineScope ist ein Kotlin-Interface, das den Lebenszyklus einer Koroutine definiert und einen Kontext zum Starten neuer Koroutinen bereitstellt. Laut der Kotlin-Dokumentation, 2025 enthält jede CoroutineScope-Instanz einen CoroutineContext und verwaltet alle darin gestarteten Koroutinen. Wenn der Scope abgebrochen (cancel) wird, werden alle untergeordneten Koroutinen automatisch abgebrochen, was Speicherlecks verhindert.
Wichtigste Punkte
CoroutineScope ist ein fundamentales Interface aus der Bibliothek kotlinx.coroutines, das als Container für Koroutinen dient. Es definiert die Grenzen des Koroutinen-Lebenszyklus: Wenn der Scope abgeschlossen wird, werden alle Koroutinen darin automatisch abgebrochen.
public interface CoroutineScope {
public val coroutineContext: CoroutineContext
}
Das Interface enthält nur ein Feld — coroutineContext. Darüber stellt der Scope einen Dispatcher, einen Job, einen Ausnahmebehandler und andere Kontextelemente für alle darin gestarteten Koroutinen bereit.
Alle Funktionen zum Starten von Koroutinen — launch, async, runBlocking — sind Erweiterungsfunktionen auf CoroutineScope. Das bedeutet, sie können nur aufgerufen werden, wenn ein Scope-Objekt verfügbar ist. Dieses Design stellt sicher, dass jede Koroutine einen klar definierten übergeordneten und Lebenszyklus hat.
In Android hat jede Architekturkomponente ihren eigenen Scope: viewModelScope für ViewModel, lifecycleScope für Activity/Fragment. In Serveranwendungen kann ein Scope an eine HTTP-Anfrage oder einen Datenbankverbindungspool gebunden werden.
Das Verständnis der inneren Arbeitsweise von CoroutineScope erfordert Vertrautheit mit dem Konzept des Job und dem Prinzip der strukturierten Nebenläufigkeit.
Jede Koroutine gibt beim Start ein Job-Objekt (oder Deferred für async) zurück. Job repräsentiert eine Aufgabe mit einem endlichen Lebenszyklus: New, Active, Completing, Completed, Cancelling, Cancelled. Job-Objekte bilden eine Baumstruktur:
Strukturierte Nebenläufigkeit ist ein zentrales Architekturprinzip von Kotlin Coroutines, bei dem der Lebenszyklus der Koroutine an den Lebenszyklus ihres Scopes gebunden ist. Dies steht im Gegensatz zum „Feuer-und-vergiss“-Modell, bei dem eine Koroutine nach Abschluss des Scopes weiterlebt. Vorteile der strukturierten Nebenläufigkeit:
Wenn scope.cancel() aufgerufen wird, wechselt der Job des Scopes in den Zustand Cancelled, was rekursiv alle untergeordneten Jobs abbricht. Nach dem Abbruch kann der Scope nur wiederverwendet werden, wenn eine neue CoroutineScope-Instanz erstellt wird.
Sie können einen CoroutineScope über eine Factory-Funktion oder durch Implementierung des Interfaces in Ihrer Klasse erstellen. Sehen wir uns beide Ansätze an.
val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
scope.launch {
println("Running on ${Thread.currentThread().name}")
}
Die Factory-Funktion nimmt einen CoroutineContext entgegen und erstellt einen Scope mit dem angegebenen Kontext. Das Beispiel verwendet Dispatchers.Default für CPU-intensive Aufgaben und SupervisorJob, der Ausnahmen zwischen untergeordneten Koroutinen isoliert.
class MyRepository {
private val scope = CoroutineScope(Dispatchers.IO + Job())
suspend fun fetchData(): Data = scope.async {
api.getData()
}.await()
fun cleanup() {
scope.cancel()
}
}
Wir speichern den Scope als Klassenfeld und rufen manuell cleanup auf, um ihn abzubrechen. Dieser Ansatz eignet sich für Komponenten mit verwaltetem Lebenszyklus — zum Beispiel Repositories oder Manager.
Kotlin ermöglicht die Delegation der CoroutineScope-Implementierung über das Schlüsselwort by:
class DataLoader : CoroutineScope by CoroutineScope(Dispatchers.IO) {
fun load() {
launch {
// coroutine runs in DataLoader scope
}
}
}
Dieser Ansatz ist praktisch, wenn die Klasse selbst ein Scope ist und Methoden zum Starten von Koroutinen bereitstellen möchte. Seien Sie jedoch vorsichtig: Die Klasse erbt alle Methoden von CoroutineScope, einschließlich cancel, was die Kapselung brechen kann.
GlobalScope ist ein Singleton-CoroutineScope für die gesamte Anwendung. Seine Verwendung in Produktionscode wird offiziell nicht empfohlen.
JetBrains erlaubt GlobalScope nur in seltenen Szenarien: anwendungsweite Hintergrundprozesse, die auch nach dem Schließen aller Activities weiterlaufen sollen (z. B. Datensynchronisation, Analytik). Aber selbst in diesen Fällen ist es vorzuziehen, einen eigenen Scope mit CoroutineScope(SupervisorJob()) zu erstellen.
Verwenden Sie immer einen benutzerdefinierten CoroutineScope mit explizitem Lebenszyklusmanagement. In Android sind dies viewModelScope und lifecycleScope. In Serveranwendungen erstellen Sie einen Scope für jede Anfrage oder jeden Verbindungspool.
Beide Funktionen sind Suspend-Funktionen, die einen temporären Scope für parallele Aufgaben erstellen, aber ihr Verhalten bei Ausnahmen unterscheidet sich grundlegend.
| Merkmal | coroutineScope | supervisorScope |
|---|---|---|
| Verhalten bei Fehlern | Eine Ausnahme in einer untergeordneten Koroutine bricht alle anderen ab | Eine Ausnahme in einer untergeordneten Koroutine bricht die anderen NICHT ab |
| Fehlerweitergabe | Ja, die erste Ausnahme wird nach außen weitergegeben | Ja, die erste Ausnahme wird nach außen weitergegeben |
| Standard-Job | Job() — Kinder sind an den Übergeordneten gebunden | SupervisorJob() — Kinder sind voneinander unabhängig |
| Typischer Anwendungsfall | Atomare Operation mit mehreren Schritten | Unabhängige parallele Aufgaben (UI-Ladevorgänge) |
Verwenden Sie coroutineScope, wenn mehrere parallele Operationen eine einzige atomare Operation bilden. Zum Beispiel das Laden von Daten von drei Servern: Wenn eine Anfrage fehlschlägt, sind die übrigen sinnlos.
suspend fun loadProductPage(): ProductPage = coroutineScope {
val product = async { api.getProduct() }
val reviews = async { api.getReviews() }
ProductPage(product.await(), reviews.await())
}
Wenn getProduct oder getReviews eine Ausnahme auslöst — werden beide Koroutinen abgebrochen und die Ausnahme an den aufrufenden Code weitergegeben.
Verwenden Sie supervisorScope, wenn parallele Operationen nicht voneinander abhängen. Zum Beispiel das Laden von Profildaten in mehreren unabhängigen Abschnitten: Wenn der Abschnitt mit Empfehlungen fehlschlägt, sollten die Profilkopfzeile und die Freundesliste angezeigt werden.
Betrachten wir die häufigsten Entwicklerfehler bei der Verwendung von CoroutineScope in Kotlin.
Das häufigste Szenario für Koroutinen-Leaks ist das Erstellen eines Scopes ohne Aufruf von cancel beim Beenden der Komponente. Wenn der Scope nicht abgebrochen wird, laufen die Koroutinen weiter und halten Referenzen auf Objekte. Verwenden Sie in Android viewModelScope oder lifecycleScope, die automatisch abgebrochen werden.
GlobalScope ignoriert den Android-Komponentenlebenszyklus. Eine in GlobalScope gestartete Koroutine, nachdem eine Activity geschlossen wurde, wird weiter ausgeführt und versucht, die UI zu aktualisieren — was zu einem Absturz führt. Verwenden Sie für UI-Komponenten immer lifecycleScope.
Nach dem Aufruf von cancel() kann der Scope nicht wiederverwendet werden — alle Koroutinen darin sind bereits abgeschlossen. Erstellen Sie eine neue CoroutineScope-Instanz über die Factory-Funktion. Job() unterstützt keine Reaktivierung.
Bei der Delegation mit by erhält die Klasse eine öffentliche cancel()-Methode, die von überall aufgerufen werden kann und die Kapselung bricht. Speichern Sie den Scope als privates Feld, anstatt das Interface zu delegieren.
Häufig gestellte Fragen
CoroutineScope ist ein Interface, das einen CoroutineContext besitzt und für den Lebenszyklus der Koroutinen verantwortlich ist. CoroutineContext ist eine Menge von Elementen (Dispatcher, Job, Fehlerbehandler), die definiert, „wie“ eine Koroutine ausgeführt wird. Ein Unterschied: Der Scope erstellt Koroutinen, während der Kontext ihr Verhalten steuert.
Ja, das ist ein Standardmuster: CoroutineScope(Dispatchers.IO + SupervisorJob()). SupervisorJob verhindert das kaskadierende Abbrechen untergeordneter Koroutinen, wenn eine von ihnen eine Ausnahme auslöst. Dies ist nützlich für unabhängige parallele Aufgaben, bei denen ein Fehler in einer nicht die anderen stoppen soll.
Es gibt keine Begrenzung der Anzahl von Koroutinen in einem Scope — sie sind nur durch den verfügbaren Speicher und die Dispatcher-Einstellungen begrenzt. Die praktische Grenze liegt typischerweise bei Tausenden aktiven Koroutinen in einem einzigen Scope. Eine große Anzahl von Koroutinen kann jedoch auf architektonische Probleme hinweisen.
Der richtige Weg ist, den Scope über den Konstruktor an die Klasse zu übergeben oder runBlockingTest / runTest aus kotlinx-coroutines-test zu verwenden. In Tests können Sie den Scope durch TestCoroutineDispatcher ersetzen und die Koroutinenausführung manuell steuern.
Nein, ein Scope ist ein externer Container für eine Koroutine. Die Koroutine selbst ist kein Scope. Innerhalb einer Koroutine können Sie jedoch einen neuen Scope über coroutineScope oder supervisorScope erstellen, um untergeordnete Koroutinen parallel zu starten.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch