Kotlin Coroutine Dispatchers
A coroutine is a task that runs without blocking everything else. A dispatcher decides which worker thread executes that task. Picking the right dispatcher keeps an app smooth, because heavy work stays away from the thread that draws the screen.
The Restaurant Kitchen
Consider a restaurant with three kinds of staff. The waiter greets guests and must never leave the dining room, or the guests feel ignored. The chefs handle heavy cooking work. The delivery team spends most of the time waiting for traffic and doorbells. Each group fits a different type of job. Dispatchers assign coroutines to those groups.
+------------------+ +--------------------+ +-----------------+ | Dispatchers.Main | | Dispatchers.Default| | Dispatchers.IO | | Waiter | | Chefs | | Delivery team | | Screen updates | | Heavy calculations | | Files, network, | | Quick tasks | | Sorting, parsing | | database waits | +------------------+ +--------------------+ +-----------------+
Setup
Add the coroutines library to the project. The Android version of the library adds the Main dispatcher.
import kotlinx.coroutines.*The Main Dispatcher
The Main dispatcher runs code on the user interface thread. Android requires every screen change to happen there. Keep the work short, because a slow task freezes buttons and animations.
The Default Dispatcher
The Default dispatcher serves tasks that burn processor time: sorting large lists, resizing images, or parsing big text. Its pool of threads matches the number of processor cores, with a minimum of two.
fun main() = runBlocking {
launch(Dispatchers.Default) {
val total = (1..1_000_000).sumOf { it.toLong() }
println("Sum: $total on ${Thread.currentThread().name}")
}
}The IO Dispatcher
The IO dispatcher serves tasks that wait: reading files, calling web servers, and querying databases. The thread sits idle while data travels, so this dispatcher allows many threads at once, with a default limit of 64.
fun main() = runBlocking {
launch(Dispatchers.IO) {
println("Reading file on ${Thread.currentThread().name}")
}
}Thread names such as DefaultDispatcher-worker-1 in the output confirm that the pool took the job.
The Unconfined Dispatcher
The Unconfined dispatcher starts a coroutine in the caller's thread and resumes it in whatever thread finishes the suspended work. The behavior surprises beginners. Use it only for rare, advanced needs and for specific tests.
Switching with withContext
The function withContext moves a block of code to another dispatcher, waits for the result, and comes back. This pattern keeps screen code tidy.
suspend fun loadUserName(): String {
return withContext(Dispatchers.IO) {
delay(500) // pretend network call
"Asha"
}
}
fun main() = runBlocking {
val name = loadUserName()
println("Welcome, $name")
}
Main thread: start --- waits --------------- shows result
\ /
IO thread: loadUserName() ---- done
The Main thread stays free while the IO thread does the waiting work.
A Typical Android Pattern
viewModelScope.launch { // starts on Main
val data = withContext(Dispatchers.IO) { // switch for loading
repository.loadData()
}
textView.text = data // back on Main
}Limiting Parallel Work
The function limitedParallelism creates a view of a dispatcher with a smaller thread limit. Use it when a resource, such as a single file, allows only a few visitors.
val oneAtATime = Dispatchers.IO.limitedParallelism(1)Choosing a Dispatcher
| Task | Dispatcher |
|---|---|
| Update text, buttons, images | Main |
| Sort or compute large data | Default |
| Network, file, database | IO |
| Special tests or tricks | Unconfined |
Common Mistakes
- Running long calculations on Main, which freezes the screen.
- Updating the screen from IO, which crashes on Android.
- Calling blocking functions inside Default, which starves the pool.
