withContext is een functie die de uitvoeringscontext binnen een coroutine wisselt, tijdelijk de thread of dispatcher voor een opgegeven codeblok wijzigt en het resultaat teruggeeft aan de oorspronkelijke context. Volgens JetBrains, 2025 is withContext een van de meest gebruikte tools van coroutines voor netwerkverzoeken en schijfbewerkingen. De functie garandeert dat na voltooiing van het blok de coroutine verder gaat op de oorspronkelijke dispatcher, wat onbedoelde thread-veiligheidsfouten voorkomt.
Belangrijkste punten
withContext is een onderbrekende functie uit het pakket kotlinx.coroutines die het opgegeven codeblok uitvoert in een gespecificeerde CoroutineContext en het resultaat teruggeeft aan de oorspronkelijke context. De functiehandtekening ziet er als volgt uit:
suspend fun withContext (
context: CoroutineContext,
block: suspend CoroutineScope.() -> T
): T
De parameter context accepteert elke CoroutineContext — meestal een van de standaard Dispatchers.IO, Dispatchers.Default of Dispatchers.Main. Het blok wordt precies in deze context uitgevoerd en het resultaat wordt teruggegeven aan de plek vanwaar withContext werd aangeroepen.
Na voltooiing van de lambda schakelt withContext gegarandeerd terug naar de oorspronkelijke dispatcher. Dit betekent dat de ontwikkelaar niet handmatig withContext(Dispatchers.Main) hoeft aan te roepen na een achtergrondbewerking — de terugkeer gebeurt automatisch. Dit gedrag is vastgelegd in de specificatie van Kotlin Coroutines sinds versie 1.3.
Android-ontwikkeling — het belangrijkste toepassingsgebied van withContext. Een typisch scenario: ViewModel start een coroutine op de hoofdthread, daarbinnen wordt withContext(Dispatchers.IO) aangeroepen voor een netwerkverzoek, en het resultaat wordt na automatische terugkeer naar Main gebruikt om de UI bij te werken. Deze benadering vormt de basis van de MVVM-architectuur en wordt aanbevolen door Google in de officiële gids over coroutines.
Om withContext te begrijpen, moet u CoroutineContext en het belangrijkste onderdeel ervan — de dispatcher — leren kennen. Elke coroutine heeft een reeks context-elementen, waaronder de dispatcher bepaalt op welke thread of threadpool de code wordt uitgevoerd.
| Dispatcher | Doel | Poolgrootte |
|---|---|---|
| Dispatchers.Main | Hoofd UI-thread (Android, JavaFX, Swing) | 1 (hoofdthread) |
| Dispatchers.IO | Schijf- en netwerkbewerkingen | 64 threads (limiet groeit) |
| Dispatchers.Default | CPU-intensieve berekeningen | max(2, aantal kernen) |
| Dispatchers.Unconfined | Zonder vaste thread | onbeperkt |
Het is belangrijk te begrijpen dat withContext geen nieuwe coroutine creëert — het verandert alleen de context voor de bestaande. Dit is het belangrijkste verschil met launch en async, die nieuwe coroutines creëren. De interne implementatie van withContext is geoptimaliseerd: als de gevraagde context overeenkomt met de huidige, vindt er geen wisseling plaats — de functie wordt uitgevoerd op dezelfde dispatcher.
Dispatchers.Main binnen withContext(Dispatchers.Main) veroorzaakt geen wisseling — Kotlin Coroutines herkent de identiteit van de contexten en slaat de overbodige bewerking over. Evenzo creëert withContext(Dispatchers.Default) binnen een coroutine die al op Default draait geen overhead. Deze optimalisatie is geïmplementeerd in ContinuationInterceptor.
Beginners verwarren withContext vaak met launch en async, omdat alle drie de functies met coroutines en context werken. Hun doel is echter fundamenteel verschillend.
| Kenmerk | withContext | launch | async |
|---|---|---|---|
| Creëert nieuwe coroutine | Nee | Ja | Ja |
| Retourneert resultaat | Ja (T direct) | Nee (Job) | Ja (Deferred<T>) |
| Uitvoering | Sequentiële | Parallel | Parallel |
| Wachten op resultaat | Automatisch | join() | await() |
| Typische use-case | Dispatcher wisselen | Fire-and-forget | Parallelle berekeningen |
Als u een enkele bewerking op een achtergrondthread moet uitvoeren en het resultaat wilt ontvangen — gebruik withContext. Als u meerdere onafhankelijke bewerkingen parallel wilt uitvoeren — gebruik async met await. Als het resultaat niet nodig is (loggen, cache schrijven) — launch. Google beveelt withContext aan als het voorkeursinstrument voor de Repository-laag in Android-architectuur.
Laten we drie praktische scenario's bekijken voor het gebruik van withContext in Android-apps in Kotlin. Elk voorbeeld toont een specifieke taak en het juiste patroon.
ViewModel roept de repository-methode aan vanuit een coroutine op Main. Binnen withContext(Dispatchers.IO) wordt het HTTP-verzoek uitgevoerd en het resultaat wordt automatisch geretourneerd:
class UserRepository(
private val api: UserApi
) {
suspend fun getUser(id: String): User {
return withContext(Dispatchers.IO) {
api.fetchUser(id)
}
}
}
De coroutine in ViewModel roept getUser aan als een gewone onderbrekende functie — zonder expliciete opgave van de dispatcher. withContext verbergt de details van thread-wisseling.
Wanneer meerdere IO-bewerkingen na elkaar moeten worden uitgevoerd, combineert withContext ze in één blok. Dit is efficiënter dan elke bewerking in een aparte withContext te wikkelen:
suspend fun loadUserProfile(id: String): Profile {
return withContext(Dispatchers.IO) {
val user = api.fetchUser(id)
val posts = api.fetchPosts(id)
Profile(user, posts)
}
}
Beide bewerkingen worden uitgevoerd op Dispatchers.IO en het Profile-resultaat wordt gecreeerd en geretourneerd zonder onnodige contextwisselingen. Als de bewerkingen onafhankelijk zijn, kunt u beter async gebruiken voor parallelle uitvoering.
In sommige scenario's moet code worden uitgevoerd die niet kan worden geannuleerd — bijvoorbeeld het opslaan van de status bij het sluiten van een scherm. De combinatie withContext + NonCancellable lost deze taak op:
withContext(Dispatchers.IO + NonCancellable) {
cache.saveState(state)
analytics.logEvent("state_saved")
}
De operator + combineert twee context-elementen: de IO-dispatcher en de NonCancellable-vlag. Het blok wordt uitgevoerd zelfs als de bovenliggende coroutine is geannuleerd — dit is nuttig voor finaliserende bewerkingen.
De interne implementatie van withContext is gebaseerd op het Continuation-mechanisme — de centrale abstractie van Kotlin-coroutines. Elk onderbrekingspunt (suspend point) slaat de uitvoeringsstatus op in een Continuation-object, en withContext is geen uitzondering.
De Kotlin-compiler vertaalt withContext naar een aanroep van de methode withContext uit kotlinx.coroutines, die intern een nieuwe instantie van DispatchedContinuation creëert. Dit object omhult de oorspronkelijke Continuation en vervangt de dispatcher erin. Als de nieuwe dispatcher verschilt van de huidige, wordt de uitvoering onderbroken, wordt het blok naar de juiste threadpool gestuurd en na voltooiing — hervat met de oorspronkelijke context.
Wanneer withContext wordt aangeroepen met dezelfde dispatcher waarop de coroutine al draait, activeert Kotlin een fast-path: het blok wordt synchroon uitgevoerd, zonder DispatchedContinuation te creëren en zonder naar de threadpool te sturen. Dit maakt withContext praktisch gratis bij herhaalde aanroepen met dezelfde context. Volgens JetBrains-benchmarks (kotlinx.coroutines 1.8) wordt fast-path uitgevoerd in minder dan 0,1 μs.
Elke aanroep van withContext met een andere dispatcher creëert een nieuwe DispatchedContinuation en vereist thread-wisseling — dit duurt 1 tot 5 μs, afhankelijk van de belasting. Voor de meeste applicaties is deze vertraging onmerkbaar, maar in lussen met duizenden iteraties kunt u de bewerkingen beter aggregeren in één withContext-blok.
Zelfs ervaren ontwikkelaars maken fouten bij het werken met withContext. Laten we vier veelvoorkomende problemen en manieren om ze te voorkomen bekijken.
Ontwikkelaars wikkelen vaak elke regel in een aparte withContext, in plaats van de bewerkingen in één blok te combineren. Elke extra aanroep met een andere dispatcher creëert overhead.
Juist: combineer sequentiële IO-bewerkingen in één withContext(Dispatchers.IO) { ... }. Als een deel van de bewerkingen CPU-intensief is — gebruik withContext(Dispatchers.Default) binnen hetzelfde blok.
withContext voert code sequentieel uit. Als twee onafhankelijke netwerkverzoeken in één withContext worden gewikkeld, worden ze één voor één uitgevoerd. Gebruik voor parallellisme async + await.
// Sequentiële — langzaam
withContext(Dispatchers.IO) {
val a = api.fetchA()
val b = api.fetchB()
}
// Parallel — snel
coroutineScope {
val a = async { api.fetchA() }
val b = async { api.fetchB() }
println("${a.await()} ${b.await()}")
}
Als de coroutine wordt geannuleerd tijdens withContext, wordt het blok op Dispatchers.IO ook onderbroken. Voor bewerkingen die koste wat kost moeten worden voltooid (databaseschrijven, analyse verzenden), combineer withContext met NonCancellable.
Werk nooit View-componenten bij binnen withContext(Dispatchers.IO). withContext keert pas terug naar Main na voltooiing van het hele blok. Voer de UI-update uit na de afsluitende accolade van withContext — dan bevindt de coroutine zich al op de hoofdthread.
Veelgestelde vragen
withContext is een onderbrekende functie die de thread niet blokkeert, maar de context binnen een bestaande coroutine wisselt. runBlocking is een brug tussen coroutines en gewone code die de huidige thread blokkeert tot voltooiing. withContext is veilig voor de UI-thread, runBlocking niet.
Nee, withContext is een onderbrekende functie, dus kan het alleen worden aangeroepen vanuit een andere onderbrekende functie of vanuit een coroutine (launch/async). Vanuit een gewone functie kan withContext niet worden aangeroepen — daarvoor is runBlocking of CoroutineScope nodig.
Kotlin activeert fast-path — het blok wordt synchroon uitgevoerd op dezelfde thread zonder wisseling. De overhead is minder dan 0,1 μs. Dit is geen fout, maar een dergelijke aanroep is overbodig — voer de code gewoon uit zonder withContext.
Uitzonderingen binnen withContext worden op dezelfde manier doorgegeven als in gewone code — via try-catch. Als het blok een uitzondering gooit, verspreidt deze zich naar de bovenliggende coroutine en annuleert deze als deze niet wordt afgehandeld. Gebruik try-catch binnen withContext of eromheen.
Nee, withContext creëert geen nieuwe coroutine. Het gebruikt de bestaande coroutine, maar wijzigt tijdelijk de context. Dit onderscheidt het van launch en async, die kind-coroutines creëren. Het gedrag wordt bevestigd door de broncode van kotlinx.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