Dispatchers, Cancellation & Flow
Dispatchers & withContext
suspend fun loadUser(id: Long): User = withContext(Dispatchers.IO) {
database.getUser(id) // temporarily switch to IO dispatcher, then return
}A Dispatcher determines which thread pool a coroutine's code runs on — Dispatchers.IO for blocking I/O, Default for CPU-intensive work, Main for UI-thread work on Android. withContext temporarily switches dispatchers for a block, then returns to the original one — the standard idiom for moving a specific operation off the main thread.
Cooperative Cancellation
Cancellation is cooperative, not preemptive: a coroutine's code must check for and respond to cancellation (checked automatically at suspension points, or explicitly via isActive/ensureActive()). A tight, non-suspending loop won't actually stop just because cancellation was requested — it needs an explicit check. This mirrors cooperative vs. preemptive scheduling generally: control transfers only at defined suspension points, not at arbitrary instruction boundaries.
Concurrent Awaiting & Flow
val a = async { fetchA() }
val b = async { fetchB() }
val result = a.await() + b.await() // both run concurrently, not sequentiallyLaunching independent operations concurrently (rather than awaiting each sequentially) reduces total wall-clock time. Flow extends the coroutine model to multi-value async streams over time — the coroutine analogue of an Observable — complementing a plain suspend function's single-value return.
The Tradeoff
Coroutines add real conceptual overhead (suspend functions, structured concurrency, dispatchers) that pays off clearly for complex or highly concurrent async workflows, but might be more machinery than a trivially simple, one-off asynchronous operation strictly needs.
Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free