Coroutines
Coroutines are Kotlin’s answer to async. Cooperative, lightweight (millions on one machine), composable with structured concurrency. suspend functions can pause without blocking the thread.
launch, async, withContext, structured cancellation
EXAMPLE
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
// 1) suspend function — non-blocking
suspend fun fetchUser(id: Int): User {
return withContext(Dispatchers.IO) {
api.getUser(id) // blocking I/O happens off the calling thread
}
}
// 2) launch — fire and forget (returns a Job)
fun loadProfile(scope: CoroutineScope, uid: Int) {
scope.launch {
val user = fetchUser(uid)
ui.show(user)
}
}
// 3) async — parallel results
suspend fun loadDashboard(): Dashboard = coroutineScope {
val user = async { fetchUser(uid) }
val orders = async { fetchOrders(uid) }
val posts = async { fetchPosts(uid) }
Dashboard(user.await(), orders.await(), posts.await())
}
// 4) Structured concurrency — child jobs share parent's lifetime
suspend fun safeFanOut() = coroutineScope {
launch { workOne() }
launch { workTwo() }
// If either fails or the parent is cancelled, BOTH are cancelled.
}
// 5) Switch context
suspend fun saveImage(bytes: ByteArray) {
val processed = withContext(Dispatchers.Default) { resize(bytes) } // CPU
withContext(Dispatchers.IO) { file.writeBytes(processed) } // I/O
}
// 6) Timeout
suspend fun robust() {
try {
withTimeout(5.seconds) {
slowApi.call()
}
} catch (e: TimeoutCancellationException) {
// handle
}
}
// 7) Cancellation — cooperative, must check
fun heavyLoop() = launch {
repeat(1_000_000) { i ->
ensureActive() // cancellation check
if (i % 1000 == 0) compute(i)
}
}
// 8) Exception handling
val handler = CoroutineExceptionHandler { _, e -> log.error("unhandled", e) }
scope.launch(handler) { riskyWork() }
// 9) Android — viewModelScope / lifecycleScope
// In a ViewModel:
fun login(email: String, password: String) = viewModelScope.launch {
_state.value = State.Loading
_state.value = runCatching { auth.login(email, password) }
.fold({ State.Success(it) }, { State.Failure(it.message ?: "err") })
}
// 10) Flow — async streams
val messages: Flow<Message> = flow {
while (true) {
emit(api.pollNext())
delay(1000)
}
}
viewModelScope.launch {
messages
.filter { it.unread }
.map { it.copy(rendered = render(it)) }
.flowOn(Dispatchers.Default)
.collect { ui.append(it) }
}
// 11) StateFlow / SharedFlow — hot state holders
private val _ui = MutableStateFlow<UiState>(UiState.Idle)
val ui: StateFlow<UiState> = _ui.asStateFlow()
// 12) Best practices
// • Don't run GlobalScope.launch — use a structured scope tied to lifecycle
// • Use Dispatchers.IO for blocking I/O, .Default for CPU, .Main for UI
// • Mark functions suspend when they can pause; never block in them
Why it matters
Structured concurrency makes leaks impossible: every coroutine is owned by a scope and dies with it. Avoid GlobalScope — pair every launch with a lifecycle-aware scope (Android: viewModelScope; server: a job tied to the request).
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
import kotlinx.coroutines.*
fun main() = runBlocking {
val job = launch {
delay(500)
println("hi from coroutine")
}
job.join()
}
Try it Yourself »
Exercise
Fire-and-forget coroutine builder.
scope.
{ logEvent() }
Six letters.
Discussion
Loading…