CoroutineScope — wat is het, levensbereik en werking in coroutines

Auteur: IT Sectr Gepubliceerd: 2026-06-22 Leestijd: 10 min

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 — interface met een enkel veld CoroutineContext, die de levenscyclus van coroutines definieert
  • Job — contextelement verantwoordelijk voor annulering: annulering van scope annuleert alle onderliggende coroutines
  • Structurele concurrentie — principe waarbij onderliggende coroutines aan de bovenliggende scope zijn gekoppeld
  • GlobalScope — scope voor de hele applicatie, niet aanbevolen vanwege het risico op lekken
  • supervisorScope — speciale scope waarin annulering van één onderliggende coroutine de andere niet annuleert

Wat is CoroutineScope in Kotlin?

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.

kotlin
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.

Rol in de kotlinx.coroutines-bibliotheek

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.

Waar wordt CoroutineScope toegepast

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.

Hoe werkt CoroutineScope: Job en structurele concurrentie

Inzicht in de interne structuur van CoroutineScope vereist bekendheid met het concept van Job en het principe van structurele concurrentie.

Job — de taak van de coroutine

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:

  • Bovenliggende Job — de scope waarin de coroutine is gestart
  • Onderliggende Job — elke coroutine gestart via launch/async
  • Annulering van de ouder → annulering van alle kinderen
  • Uitzondering in een kind → annulering van de ouder (behalve supervisorScope)

Principe van structurele concurrentie

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:

  • Voorspelbare levenscyclus — wanneer de scope eindigt, worden alle coroutines gegarandeerd gestopt
  • Automatische foutafhandeling — een uitzondering in een onderliggende coroutine verspreidt zich naar de scope
  • Geen lekken — geen enkele coroutine blijft actief na beëindiging van de scope
  • Duidelijke hiërarchie — de code weerspiegelt de logische structuur van parallelle bewerkingen

Levenscyclus van CoroutineScope

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 maken en configureren

CoroutineScope kan worden gemaakt via een fabrieksfunctie of door de interface in uw klasse te implementeren. Laten we beide benaderingen bekijken.

Fabrieksfunctie CoroutineScope()

kotlin
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.

Interface-implementatie via compositie

kotlin
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.

Implementatie via delegatie

Kotlin maakt het mogelijk de implementatie van CoroutineScope te delegeren via het sleutelwoord by:

kotlin
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 versus aangepaste CoroutineScope

GlobalScope — is een singleton CoroutineScope voor de hele applicatie. Het gebruik ervan in productiecode wordt officieel niet aanbevolen.

Problemen met GlobalScope

  • Gebrek aan structurele concurrentie — coroutines in GlobalScope zijn niet gekoppeld aan de levenscyclus van de component
  • Geheugenlekken — een coroutine kan blijven uitvoeren na het sluiten van Activity/Fragment
  • Moeilijk testen — GlobalScope kan niet worden vervangen in tests
  • Ongecontroleerd resourceverbruik — veel coroutines kunnen langer werken dan verwacht

Wanneer is GlobalScope gerechtvaardigd

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()).

Aanbeveling

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.

coroutineScope vs supervisorScope: wat is het verschil

Beide functies zijn suspend-functies die een tijdelijke scope maken voor parallelle taken, maar hun gedrag bij uitzonderingen verschilt fundamenteel.

KenmerkcoroutineScopesupervisorScope
Gedrag bij foutUitzondering in onderliggende coroutine annuleert alle andereUitzondering in onderliggende coroutine annuleert de andere NIET
FoutpropagatieJa, de eerste uitzondering wordt naar buiten gepropageerdJa, de eerste uitzondering wordt naar buiten gepropageerd
Standaard JobJob() — kinderen zijn gekoppeld aan de ouderSupervisorJob() — kinderen zijn niet van elkaar afhankelijk
Typisch use-caseAtomaire bewerking uit meerdere stappenOnafhankelijke parallelle taken (UI-ladingen)

Wanneer coroutineScope kiezen

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.

kotlin
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.

Wanneer supervisorScope kiezen

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.

Veelgemaakte fouten bij het werken met CoroutineScope

Laten we de meest voorkomende fouten van ontwikkelaars bij het gebruik van CoroutineScope in Kotlin bekijken.

Fout 1: Vergeten de scope te annuleren

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.

Fout 2: Gebruik van GlobalScope in Activity of Fragment

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.

Fout 3: Hergebruik van een geannuleerde scope

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.

Fout 4: Onjuiste delegatie van de CoroutineScope-interface

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

Wat is het verschil tussen CoroutineScope en CoroutineContext?

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.

Kan ik een CoroutineScope maken met SupervisorJob?

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.

Hoeveel coroutines kan een CoroutineScope bevatten?

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.

Hoe test ik code met CoroutineScope?

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.

Kan een coroutine zijn eigen scope hebben?

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

  • CoroutineScope — interface met het veld coroutineContext, die de levenscyclus definieert van coroutines die erin zijn gestart
  • Structurele concurrentie — annulering van de scope annuleert automatisch alle onderliggende coroutines, waardoor geheugenlekken worden voorkomen
  • Job en SupervisorJob — twee foutafhandelingsmodi: cascaderende annulering (Job) en geïsoleerde fouten (SupervisorJob)
  • GlobalScope — niet aanbevolen voor productie vanwege het ontbreken van koppeling aan de levenscyclus
  • coroutineScope vs supervisorScope — atomaire parallelle bewerkingen versus onafhankelijke parallelle taken
  • viewModelScope en lifecycleScope — kant-en-klare scopes voor Android, automatisch geannuleerd bij beëindiging van de component
  • Fabrieksfunctie — de voorkeursmanier om een scope te maken via CoroutineContext + expliciete cancel-aanroep

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.

Bespreek het project

Lees ook