Step 8 of 13
Suspending functions, launch/async builders, dispatchers, structured concurrency, Flow, StateFlow
Coroutines และ Flow — suspend functions, launch/async, dispatchers, structured concurrency
Coroutines are lightweight threads. They can suspend (pause) without blocking the thread, letting other code run. This lets you write async code that looks like normal sequential code.
Diagram: Threads block the OS thread. Coroutines suspend and free the thread for other work.
Loading diagram...
You need the kotlinx.coroutines library. Current version: 1.11.0.
// build.gradle.kts
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0")
}
Add the suspend keyword to declare a function that can pause:
import kotlinx.coroutines.delay
import kotlin.time.Duration.Companion.seconds
suspend fun fetchData(): String {
delay(1.seconds) // suspends for 1 second, does NOT block the thread
return "Data loaded"
}
suspend fun main() {
println("Start")
val data = fetchData()
println(data) // Data loaded
println("Done")
}
launch starts a coroutine that runs alongside others. Use it when you do not need the result immediately:
import kotlinx.coroutines.*
import kotlin.time.Duration.Companion.milliseconds
suspend fun main() = coroutineScope {
launch {
delay(300.milliseconds)
println("Task 1 done")
}
launch {
delay(100.milliseconds)
println("Task 2 done")
}
println("Both tasks launched")
}
// Output:
// Both tasks launched
// Task 2 done
// Task 1 done
async returns a Deferred<T> — a promise of a future result. Use .await() to get the value:
import kotlinx.coroutines.*
import kotlin.time.Duration.Companion.milliseconds
suspend fun main() = coroutineScope {
val deferred1 = async {
delay(200.milliseconds)
"Result 1"
}
val deferred2 = async {
delay(300.milliseconds)
"Result 2"
}
// Both run in parallel — total time ~300ms, not 500ms
println(deferred1.await()) // Result 1
println(deferred2.await()) // Result 2
}
Dispatchers control which threads coroutines run on:
| Dispatcher | Use for | Threads |
|---|---|---|
Dispatchers.Default | CPU-heavy work | Shared pool (CPU cores) |
Dispatchers.IO | Network, file I/O | Large shared pool (64+) |
Dispatchers.Main | UI updates (Android) | Single main thread |
import kotlinx.coroutines.*
suspend fun main() {
withContext(Dispatchers.Default) {
println("CPU work on: ${Thread.currentThread().name}")
}
withContext(Dispatchers.IO) {
println("I/O work on: ${Thread.currentThread().name}")
}
}
Every coroutine runs inside a CoroutineScope. A parent scope waits for all children to finish. If a child fails, the parent cancels all other children:
suspend fun main() = coroutineScope {
launch {
delay(500.milliseconds)
println("Child 1 done")
}
launch {
delay(300.milliseconds)
println("Child 2 done")
}
// coroutineScope waits for ALL children before returning
println("Waiting for children...")
}
// Output:
// Waiting for children...
// Child 2 done
// Child 1 done
Flow is like a list that produces values over time. It is cold — nothing runs until you collect:
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlin.time.Duration.Companion.milliseconds
fun countdown(n: Int): Flow<Int> = flow {
for (i in n downTo 1) {
delay(100.milliseconds)
emit(i)
}
}
suspend fun main() {
countdown(3).collect { value ->
println("Tick: $value")
}
println("Liftoff!")
}
// Output:
// Tick: 3
// Tick: 2
// Tick: 1
// Liftoff!
Transform and filter flows just like collections:
fun numbers(): Flow<Int> = flow {
for (i in 1..5) {
delay(100.milliseconds)
emit(i)
}
}
suspend fun main() {
numbers()
.map { it * it } // square each number
.filter { it > 5 } // keep values > 5
.collect { println(it) } // 9, 16, 25
}
StateFlow always has a value. It is hot — all collectors share the same stream:
import kotlinx.coroutines.flow.*
val state = MutableStateFlow(0)
suspend fun main() = coroutineScope {
launch {
state.collect { value ->
println("Observer: $value")
}
}
delay(100)
state.value = 1
delay(100)
state.value = 2
delay(100)
state.value = 3
}
// Observer: 0 → 1 → 2 → 3
Fetch data from multiple sources in parallel and combine the results:
import kotlinx.coroutines.*
import kotlin.time.Duration.Companion.milliseconds
suspend fun fetchUserProfile(): String {
delay(100.milliseconds)
return "Alice, age 30"
}
suspend fun fetchUserOrders(): Int {
delay(150.milliseconds)
return 42
}
suspend fun fetchUserCredits(): Double {
delay(120.milliseconds)
return 1250.50
}
suspend fun main() = coroutineScope {
val profile = async { fetchUserProfile() }
val orders = async { fetchUserOrders() }
val credits = async { fetchUserCredits() }
// All three run in parallel (~150ms total, not 370ms)
println("Profile: ${profile.await()}")
println("Orders: ${orders.await()}")
println("Credits: $${credits.await()}")
}