Reading — step 1 of 6
Learn
Coroutines are Kotlin's flagship concurrency feature — covered in chapters 14-15 of Kotlin in Action and the entire kotlinx.coroutines reference. They let you write async code that LOOKS sequential.
suspend functions
A function marked suspend can pause and resume without blocking a thread:
import kotlinx.coroutines.*
suspend fun fetchUser(id: Int): String {
delay(100) // suspends, doesn't block
return "User \$id"
}
You can only call suspend functions from another suspend function or a coroutine builder (next section). The compiler enforces this — it transforms suspend functions into state machines under the hood.
Coroutine builders
fun main() = runBlocking {
val user = fetchUser(42)
println(user)
}
runBlocking { }— bridges blocking to coroutine code. Use formainor tests; not in real apps.launch { }— fire-and-forget; returns a Job (cancellable handle).async { }— like launch but returns a Deferred<T> with a result.
fun main() = runBlocking {
// Parallel via async:
val a = async { fetchUser(1) }
val b = async { fetchUser(2) }
println("\${a.await()} and \${b.await()}") // ~100ms total, not 200ms
}
CoroutineScope
Every coroutine runs in a SCOPE. The scope tracks all running coroutines and can cancel them en masse:
val scope = CoroutineScope(Dispatchers.Default)
val job = scope.launch {
while (isActive) {
// ... work ...
delay(100)
}
}
// Later:
job.cancel() // cancels just this one
scope.cancel() // cancels everything in the scope
Dispatchers — which thread
launch(Dispatchers.IO) { /* blocking I/O work */ }
launch(Dispatchers.Default) { /* CPU-bound work */ }
launch(Dispatchers.Main) { /* Android UI updates */ }
Dispatchers route coroutines to thread pools tuned for the workload type.
Cancellation is cooperative
val job = launch {
repeat(1000) { i ->
ensureActive() // throws if cancelled
// or check: if (!isActive) return@launch
println(i)
delay(10)
}
}
delay(100)
job.cancel()
Most stdlib suspend functions check cancellation automatically. Long custom loops should call ensureActive() periodically.
Structured concurrency
launch and async are bound to their parent scope. The scope doesn't complete until all children do — and a failure in one child cancels siblings:
coroutineScope {
launch { stepA() }
launch { stepB() }
} // both A and B must finish before this block returns
This prevents the classic concurrency leak where a failed task leaves orphans running.
Common mistakes
- Using
runBlockingin production app code — blocks the calling thread. Reserve for entry points and tests. asyncwithoutawait— exceptions inside silently swallow. Always await or use launch.- Forgetting cancellation — long-running coroutines should call
ensureActive()to honor cancel requests. - Using
GlobalScope.launch— leaks. Use a structured scope with a clear lifecycle.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…