Dispatchers
Coroutine dispatchers control which thread pool coroutines run on. Dispatchers.IO for blocking I/O, Dispatchers.Default for CPU work, Dispatchers.Main for UI (Android/JavaFX). Switch with withContext, parallelise across them, and the right choice keeps your app responsive.
IO, Default, Main, withContext, custom
EXAMPLE
import kotlinx.coroutines.*
// 1) Built-in dispatchers
// • Dispatchers.Main — UI thread (Android/JavaFX); blocked = ANR
// • Dispatchers.Main.immediate — runs immediately if already on Main
// • Dispatchers.Default — CPU-bound work; pool size = number of cores
// • Dispatchers.IO — blocking I/O; pool size = 64 (default; can be tuned)
// • Dispatchers.Unconfined — runs on caller's thread; for tests + special cases
// 2) Switch context with withContext
suspend fun loadAndDisplay() {
val data = withContext(Dispatchers.IO) {
api.fetchData() // blocking-ish HTTP call
}
withContext(Dispatchers.Main) {
textView.text = data // UI update
}
}
// Or stay on the IO pool for the whole operation:
suspend fun loadAndDisplay2() = withContext(Dispatchers.IO) {
val data = api.fetchData()
withContext(Dispatchers.Main) {
textView.text = data
}
}
// 3) Launch on a specific dispatcher
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
scope.launch { compute() }
scope.launch(Dispatchers.IO) { saveToDisk() }
// 4) Default vs IO — when to pick which
// Default: CPU-bound (parsing, image processing, ML inference)
// IO: blocking calls that wait (HTTP, file I/O, JDBC)
//
// The distinction matters because:
// • Default pool = small (≈ cores); can't be exhausted by waiting threads
// • IO pool = large (≈ 64); designed for many blocking calls in parallel
suspend fun heavyJob() = withContext(Dispatchers.Default) {
parseHugeJson(input) // CPU
}
suspend fun networkJob() = withContext(Dispatchers.IO) {
URL("https://api.example.com").readText() // blocks waiting for response
}
// 5) Parallel decomposition
suspend fun loadDashboard() = coroutineScope {
val user = async(Dispatchers.IO) { api.fetchUser() }
val posts = async(Dispatchers.IO) { api.fetchPosts() }
val cpu = async(Dispatchers.Default) { computeStats() }
Triple(user.await(), posts.await(), cpu.await())
}
// async runs in parallel — total time = max, not sum.
// 6) Custom dispatchers
import java.util.concurrent.Executors
val databaseDispatcher = Executors.newFixedThreadPool(4).asCoroutineDispatcher()
scope.launch(databaseDispatcher) {
db.runHeavyQuery()
}
// Remember to close:
databaseDispatcher.close() // on app shutdown
// 7) Android viewModelScope + lifecycleScope
// They use Dispatchers.Main.immediate by default.
// Switch inside to IO for blocking work.
class MyViewModel : ViewModel() {
fun load() = viewModelScope.launch {
val data = withContext(Dispatchers.IO) { api.fetch() }
_data.value = data // back on Main automatically
}
}
// 8) Don't block the dispatcher
scope.launch(Dispatchers.Main) {
Thread.sleep(5000) // BLOCKS UI; ANR; very bad
delay(5000) // SUSPENDS; UI responsive
}
// Always use coroutine-friendly primitives:
// • delay() instead of Thread.sleep()
// • Mutex.withLock instead of synchronized()
// • Channels instead of blocking queues
// • flow operators instead of blocking iteration
// 9) Long-running CPU work — yield()
scope.launch(Dispatchers.Default) {
for (i in 0 until 1_000_000) {
compute(i)
if (i % 1000 == 0) yield() // give other coroutines a turn
}
}
// 10) IO dispatcher tuning
val ioDispatcher = Dispatchers.IO.limitedParallelism(32) // cap at 32 (default 64)
scope.launch(ioDispatcher) { ... }
// Reduce when downstream is the bottleneck (DB connection pool, rate-limited API).
// 11) Testing — TestDispatcher
import kotlinx.coroutines.test.*
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun example() = runTest {
val dispatcher = StandardTestDispatcher(testScheduler)
val scope = CoroutineScope(dispatcher)
val result = scope.async { computeAsync() }
advanceTimeBy(1000) // skip delays
assertEquals(42, result.await())
}
// Replace Dispatchers.Main in tests:
Dispatchers.setMain(StandardTestDispatcher())
// ...
Dispatchers.resetMain()
// 12) coroutineContext + inheritance
launch(Dispatchers.IO) {
println(coroutineContext[CoroutineDispatcher::class]) // Dispatchers.IO
launch {
// INHERITS Dispatchers.IO; no need to specify
}
launch(Dispatchers.Default) {
// Explicit override
}
}
// 13) Mixing dispatchers in flows
flow {
emit(file.readText()) // CPU-heavy on Default
}
.flowOn(Dispatchers.IO) // moves UPSTREAM emits to IO
.collect { /* by default runs on caller; switch with .flowOn if needed */ }
// 14) When to use Dispatchers.Unconfined
// Almost never in production code.
// Useful in tests when you want immediate, deterministic execution.
// Otherwise causes weird threading behaviour because it doesn't confine the coroutine.
// 15) Common bugs
// • Calling blocking I/O on Main → ANR
// • Using Dispatchers.Default for HTTP → starves CPU pool
// • Forgetting withContext(Dispatchers.Main) before UI update → wrong thread
// • Creating custom dispatcher in a request handler → thread leak; create once at startup
// • Holding a Mutex across dispatcher switch → deadlock possible; release before await
// • Cancellation not propagated — use isActive / yield in tight loops
// • limitedParallelism set too low → bottleneck under load
// • TestDispatcher confusion — use runTest + advanceTimeBy for delay simulation
// • Mixing GlobalScope + Dispatchers.Default — easy way to leak coroutines
// • async + Dispatcher misuse → all work serialised when expected parallelism
Why it matters
Pick Dispatchers.IO for blocking calls (HTTP, file, JDBC), Default for CPU work, Main for UI. Switch with withContext, parallelise across them with async, and never block the Main dispatcher — use delay, Mutex.withLock, and coroutine-friendly primitives. Cap parallelism with limitedParallelism when downstream is the bottleneck.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
withContext(Dispatchers.IO) {
File("big.txt").readText()
}
withContext(Dispatchers.Main) {
progressBar.visibility = View.GONE
}
Try it Yourself »
Discussion
Loading…