Coroutines: suspend, scopes, dispatchers
A coroutine is a suspendable unit of computation: it can wait without blocking a thread. One thread can run thousands of coroutines.
Core concepts:
- suspend fun — a suspendable function; callable only from a coroutine or another suspend function.
suspenditself does not switch threads — it only marks suspension points. - CoroutineScope — manages coroutine lifetimes. Android ships ready scopes:
viewModelScope,lifecycleScope. - Job — a coroutine's cancellable handle; a SupervisorJob keeps one child's failure from cancelling its siblings.
- Dispatchers:
Main(UI),IO(network/disk),Default(CPU-bound work). - structured concurrency — every coroutine belongs to a scope: cancelling the scope cancels all children, so no coroutine is ever "leaked".
kotlin
class AccountViewModel(private val repo: AccountRepository) : ViewModel() {
fun loadBalance() {
viewModelScope.launch { // Main dispatcher-də başlayır
_uiState.update { it.copy(isLoading = true) }
try {
val balance = withContext(Dispatchers.IO) {
repo.fetchBalance() // şəbəkə çağırışı IO thread pool-da
}
_uiState.update { it.copy(balance = balance, isLoading = false) }
} catch (e: IOException) {
_uiState.update { it.copy(error = "Şəbəkə xətası", isLoading = false) }
}
}
}
}The typical ViewModel pattern: IO work, result on Main
| Question | launch | async |
|---|---|---|
| Returns | Job (no result) | Deferred<T> — result via await() |
| Purpose | Fire-and-forget work | Results of parallel computations |
| Exception behavior | Propagates to the parent immediately | Held until await() is called |
Cancellation is cooperative: a coroutine only "sees" cancellation at suspension points (delay, withContext, yield) or by checking isActive. An infinite while(true) loop with no suspension point never cancels. Swallowing CancellationException (catching without rethrowing) is a classic bug — always rethrow it.
🛠 Practice task
Write two suspend functions that "fetch" a balance and a transaction history (simulate each with delay(1500)).
- Call them sequentially and time it (
measureTimeMillis). - Then call them in parallel with
async+awaitand time it again. - Throw an exception in one and observe the behavioural difference between
launchandasync.
Done when: you can explain both the ~3s → ~1.5s difference and the exception behaviour.
📚 Sources and documentation
- Coroutines overview — Kotlin docsofficialkotlinlang.org
- Coroutines on Androidofficialdeveloper.android.com
- Coroutines best practices — Androidofficialdeveloper.android.com