CoroutineScope — is een Kotlin-interface die het levensbereik van een coroutine definieert en context biedt voor het starten van nieuwe coroutines. Volgens de Kotlin-documentatie, 2025 bevat elk CoroutineScope-exemplaar een CoroutineContext en beheert het alle coroutines die erin zijn gestart. Wanneer de scope eindigt (cancel), worden alle onderliggende coroutines automatisch geannuleerd, wat geheugenlekken voorkomt.
Belangrijkste punten
CoroutineScope — is een fundamentele interface uit de kotlinx.coroutines-bibliotheek die dient als container voor coroutines. Het definieert de levensgrenzen van coroutines: wanneer de scope eindigt, worden alle coroutines erin automatisch geannuleerd.
public interface CoroutineScope {
public val coroutineContext: CoroutineContext
}
De interface bevat slechts één veld — coroutineContext. Via dit veld biedt de scope een dispatcher (Dispatcher), taak (Job), uitzonderingsafhandeling en andere contextelementen voor alle coroutines die erin zijn gestart.
Alle functies voor het starten van coroutines — launch, async, runBlocking — zijn extensiefuncties op CoroutineScope. Dit betekent dat ze alleen kunnen worden aangeroepen als er een scope-object aanwezig is. Een dergelijk ontwerp garandeert dat elke coroutine een duidelijk gedefinieerde ouder en levenscyclus heeft.
In Android heeft elke architectuurcomponent zijn eigen scope: viewModelScope voor ViewModel, lifecycleScope voor Activity/Fragment. In servertoepassingen kan de scope worden gekoppeld aan een HTTP-verzoek of aan een verbindingspool met de database.
Inzicht in de interne structuur van CoroutineScope vereist bekendheid met het concept van Job en het principe van structurele concurrentie.
Elke coroutine retourneert bij het starten een Job-object (of Deferred voor async). Job vertegenwoordigt een taak met een gedefinieerde levenscyclus: New, Active, Completing, Completed, Cancelling, Cancelled. Job-objecten vormen een boomstructuur:
Structurele concurrentie — het belangrijkste architectuurprincipe van Kotlin Coroutines, waarbij de levensduur van een coroutine is gekoppeld aan de levensduur van zijn scope. Dit contrasteert met het „fire-and-forget”-model, waarbij een coroutine blijft bestaan nadat de scope is beëindigd. Voordelen van structurele concurrentie:
Wanneer scope.cancel() wordt aangeroepen, gaat de Job van de scope naar de status Cancelled, wat recursief alle onderliggende Job-objecten annuleert. Na annulering kan de scope alleen opnieuw worden gebruikt door een nieuw CoroutineScope-exemplaar te maken.
CoroutineScope kan worden gemaakt via een fabrieksfunctie of door de interface in uw klasse te implementeren. Laten we beide benaderingen bekijken.
val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
scope.launch {
println("Uitvoeren op ${Thread.currentThread().name}")
}
De fabrieksfunctie accepteert CoroutineContext en maakt een scope met de opgegeven context. In het voorbeeld wordt Dispatchers.Default gebruikt voor CPU-intensieve taken en SupervisorJob die uitzonderingen tussen onderliggende coroutines isoleert.
class MyRepository {
private val scope = CoroutineScope(Dispatchers.IO + Job())
suspend fun fetchData(): Data = scope.async {
api.getData()
}.await()
fun cleanup() {
scope.cancel()
}
}
We slaan de scope op als een veld van de klasse en roepen handmatig cleanup aan om deze te annuleren. Dit is geschikt voor componenten met een beheerde levenscyclus — bijvoorbeeld voor repositories of managers.
Kotlin maakt het mogelijk de implementatie van CoroutineScope te delegeren via het sleutelwoord by:
class DataLoader : CoroutineScope by CoroutineScope(Dispatchers.IO) {
fun load() {
launch {
// coroutine draait in DataLoader-scope
}
}
}
Deze benadering is handig wanneer de klasse zelf een scope is en methoden voor het starten van coroutines wil bieden. Wees echter voorzichtig: de klasse erft alle methoden van CoroutineScope, inclusief cancel, wat de inkapseling kan schenden.
GlobalScope — is een singleton CoroutineScope voor de hele applicatie. Het gebruik ervan in productiecode wordt officieel niet aanbevolen.
JetBrains staat GlobalScope alleen toe in zeldzame scenario’s: achtergrondprocessen op applicatieniveau die moeten blijven bestaan, zelfs na het sluiten van alle Activity (bijvoorbeeld gegevenssynchronisatie, analyses). Maar zelfs in deze gevallen verdient het de voorkeur om een eigen scope te maken met CoroutineScope(SupervisorJob()).
Gebruik altijd een aangepaste CoroutineScope met expliciet levenscyclusbeheer. In Android zijn dit viewModelScope en lifecycleScope. Maak in servertoepassingen een scope voor elk verzoek of elke verbindingspool.
Beide functies zijn suspend-functies die een tijdelijke scope maken voor parallelle taken, maar hun gedrag bij uitzonderingen verschilt fundamenteel.
| Kenmerk | coroutineScope | supervisorScope |
|---|---|---|
| Gedrag bij fout | Uitzondering in onderliggende coroutine annuleert alle andere | Uitzondering in onderliggende coroutine annuleert de andere NIET |
| Foutpropagatie | Ja, de eerste uitzondering wordt naar buiten gepropageerd | Ja, de eerste uitzondering wordt naar buiten gepropageerd |
| Standaard Job | Job() — kinderen zijn gekoppeld aan de ouder | SupervisorJob() — kinderen zijn niet van elkaar afhankelijk |
| Typisch use-case | Atomaire bewerking uit meerdere stappen | Onafhankelijke parallelle taken (UI-ladingen) |
Gebruik coroutineScope wanneer meerdere parallelle bewerkingen één atomaire bewerking vormen. Bijvoorbeeld het laden van gegevens van drie servers: als één verzoek mislukt, hebben de andere geen zin.
suspend fun loadProductPage(): ProductPage = coroutineScope {
val product = async { api.getProduct() }
val reviews = async { api.getReviews() }
ProductPage(product.await(), reviews.await())
}
Als getProduct of getReviews een uitzondering gooit — worden beide coroutines geannuleerd en wordt de uitzondering naar de aanroepende code gepropageerd.
Gebruik supervisorScope wanneer parallelle bewerkingen niet van elkaar afhankelijk zijn. Bijvoorbeeld het laden van profielgegevens in meerdere onafhankelijke secties: als de aanbevelingssectie mislukt, moeten de profielkop en vriendenlijst worden weergegeven.
Laten we de meest voorkomende fouten van ontwikkelaars bij het gebruik van CoroutineScope in Kotlin bekijken.
Het meest voorkomende scenario voor coroutine-lekken — het maken van een scope zonder cancel aan te roepen bij het beëindigen van de component. Als de scope niet wordt geannuleerd, blijven coroutines actief, met referenties naar objecten. Gebruik in Android viewModelScope of lifecycleScope, die automatisch worden geannuleerd.
GlobalScope negeert de levenscyclus van Android-componenten. Een coroutine die in GlobalScope is gestart na het sluiten van Activity blijft actief en probeert de UI bij te werken — wat leidt tot een crash. Gebruik altijd lifecycleScope voor UI-componenten.
Na het aanroepen van cancel() kan de scope niet opnieuw worden gebruikt — alle coroutines erin zijn al voltooid. Maak een nieuw CoroutineScope-exemplaar via de fabrieksfunctie. Job() ondersteunt geen hernieuwde activering.
Bij delegatie via by krijgt de klasse een openbare methode cancel() die vanuit elke locatie kan worden aangeroepen, wat de inkapseling schendt. Bewaar de scope als een prive-veld, delegeer de interface niet.
Veelgestelde vragen
CoroutineScope — is een interface die CoroutineContext bezit en verantwoordelijk is voor de levenscyclus van coroutines. CoroutineContext — is een set elementen (dispatcher, job, foutafhandeling) die bepaalt „hoe” een coroutine wordt uitgevoerd. Een van de verschillen: scope creëert coroutines, context beheert hun gedrag.
Ja, dit is een standaard patroon: CoroutineScope(Dispatchers.IO + SupervisorJob()). SupervisorJob voorkomt cascaderende annulering van onderliggende coroutines bij een uitzondering in een van hen. Dit is nuttig voor onafhankelijke parallelle taken waarbij een fout in de ene de andere niet mag stoppen.
Er zijn geen beperkingen voor het aantal coroutines in een scope — ze worden alleen beperkt door het beschikbare geheugen en de dispatcher-instellingen. De praktische limiet bedraagt gewoonlijk duizenden actieve coroutines in één scope. Een groot aantal coroutines kan echter wijzen op architectuurproblemen.
De juiste manier is om de scope via de constructor aan de klasse door te geven of runBlockingTest / runTest uit kotlinx-coroutines-test te gebruiken. In tests kunt u de scope vervangen door TestCoroutineDispatcher en de uitvoering van coroutines handmatig controleren.
Nee, de scope is een externe container voor de coroutine. De coroutine zelf is geen scope. Binnen een coroutine kan echter een nieuwe scope worden gemaakt via coroutineScope of supervisorScope voor het parallel starten van onderliggende coroutines.
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook