KOTLIN CORE · 109
Coroutines:suspend 不等於「開一條 Thread」
Mobile app 要等 location、network、database,但不能因此把 UI thread 卡住。Coroutine 是 Kotlin 對非同步流程與 structured concurrency 的抽象;它和 thread 有關,但不是一個 coroutine 對應一條 OS thread。
Learning outcomes
- 能解釋
suspend表示「可在 suspension point 暫停」而不是「一定在背景 thread」。 - 能區分 coroutine、thread、dispatcher、scope。
- 能說明 structured concurrency 與 cancellation 的理由。
- 能把 async repository call 接到 domain state,而不把 blocking I/O 塞在 UI thread。
1. 先拆四個名詞
| 概念 | 先抓這個直覺 |
|---|---|
| Coroutine | 可 suspend / resume 的工作單位 |
| Thread | OS / runtime 執行 instruction 的 thread |
| Dispatcher | 決定 coroutine 工作在哪些 execution threads/context 上安排 |
| Scope | 管理一組 coroutine 的 lifecycle / parent-child 關係 |
2. suspend function 不是自動 background
suspend fun loadTripPoints(): List<TripPoint> {
return repository.fetchPoints()
}
suspend 只表示這個 function 可以呼叫其他 suspend function / 在 suspension point 暫停。它本身沒有保證「一定新開 thread」或「一定在 IO dispatcher」。
3. Suspension:等待時不必霸住同一條 thread
這和「blocking call 一直佔著 thread 等」是不同模型。
4. Dispatcher:工作性質與 execution context
withContext(Dispatchers.IO) {
repository.readLargeFile()
}
概念上,blocking I/O 不應塞在 UI/main thread。CPU-heavy work、blocking I/O、UI update 的安排策略不同。
suspend 不能直接推論「一定要包 Dispatchers.IO」。要看該 API contract。5. Structured concurrency:child work 不應變成孤兒
suspend fun refreshTrip(): TripState =
coroutineScope {
val points = async {
repository.fetchPoints()
}
val config = async {
repository.fetchConfig()
}
buildState(
points.await(),
config.await()
)
}
coroutineScope 讓 child coroutines 受 parent scope 管理;scope 不完成,child work 就不會莫名其妙脫離整個操作。
6. Cancellation:使用者離開畫面,舊工作還需要嗎?
Coroutine cancellation 通常是 cooperative。Suspend functions 常會檢查 cancellation;CPU 密集 loop 則可能需要主動建立 cancellation point。
while (isActive) {
updateComputation()
yield()
}
這個概念到 Android lifecycle / ViewModel 時會非常重要。
7. Error propagation 也是 concurrency contract 的一部分
try {
val state = refreshTrip()
render(state)
} catch (e: IOException) {
showNetworkError()
}
不要把 coroutine 當成「錯誤會自動消失」。parent-child 關係、exception propagation、supervision 都會影響 failure 行為。
8. Project checkpoint:Trip Meter Domain Core v3
class TripRepository(
private val api: TripApi
) {
suspend fun loadSnapshot(): TripState {
val response = api.fetchTrip()
return TripState(
distanceKm = response.distanceKm,
elapsedSeconds = response.elapsedSeconds,
lastPoint = response.lastPoint
)
}
}
suspend fun refresh(
repository: TripRepository
): Result<TripState> =
try {
Result.success(
repository.loadSnapshot()
)
} catch (e: Exception) {
Result.failure(e)
}
到這裡 Kotlin core 仍然不依賴 Activity。下一課才把它接進 Android lifecycle / ViewModel state。
9. JavaScript await vs Kotlin suspend:相似,但不要視為同一機制
| JavaScript | Kotlin | |
|---|---|---|
| 常見 async abstraction | Promise + async/await | Coroutine + suspend |
| Thread model | Browser/Node event-loop 模型為核心 | 可跨 dispatcher / threads 安排 |
| Cancellation | 通常需 API-specific mechanism,例如 AbortController | coroutine job / structured cancellation 是核心模型 |
Final knowledge check
- 為什麼「suspend = background thread」是錯的?
- scope 解決的不是語法問題,而是什麼 lifecycle 問題?
- 如果 CPU loop 完全沒有 suspension point,cancellation 可能遇到什麼問題?
- 畫出 Trip Meter 從 async source → repository → domain state 的資料流。