Lesson 18 / 25
Dispatchers and Flow
Choosing threads and streaming values.
Where coroutines run and how values stream
A dispatcher decides which threads run a coroutine: Dispatchers.Default for CPU-bound work, Dispatchers.IO for blocking I/O such as JDBC or file access, and Dispatchers.Main for UI threads on Android and desktop. Switch with withContext(Dispatchers.IO) { ... }; well-written suspend functions are main-safe by doing this internally. A Flow<T> is a cold asynchronous stream: the flow { emit(x) } builder runs each time it is collected. Operators like map, filter, debounce and catch transform it, flowOn changes the upstream dispatcher, and collect is the terminal operator. StateFlow and SharedFlow are hot flows for state and events shared among collectors.
A polling flow and a state holder
Cold Flow and hot StateFlow.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun prices(symbol: String): Flow<Double> = flow {
while (true) {
emit(fetchPrice(symbol)) // runs per collector
delay(1_000)
}
}.flowOn(Dispatchers.IO) // upstream runs on IO
suspend fun fetchPrice(symbol: String): Double = 100.0 // placeholder for a real call
class CounterModel {
private val _count = MutableStateFlow(0)
val count: StateFlow<Int> = _count.asStateFlow() // read-only view
fun increment() { _count.update { it + 1 } }
}
suspend fun watch() {
prices("KT")
.map { it * 1.1 }
.take(3)
.catch { e -> println("error: ${e.message}") }
.collect { println(it) }
}Expose read-only state
Keep MutableStateFlow private and expose StateFlow via asStateFlow(), so only the owner can change state and everyone else observes it.
Quick check: Which dispatcher suits blocking file or JDBC calls?
- `Dispatchers.IO`
- `Dispatchers.Main`
- `Dispatchers.Unconfined`
- No dispatcher; call them directly on the main thread
Answer
`Dispatchers.IO` — IO is sized for blocking operations; Default is for CPU work.