Android Coroutine Context 與 Dispatchers

在上一章我們學會了如何啟動 Coroutine,這一章我們要深入了解 Coroutine 的組態設定 CoroutineContext

什麼是 CoroutineContext?

每個 Coroutine 都帶有一個 CoroutineContext,它是一組元素的集合,類似於 Map。其中最重要的元素包括:

  1. Job:控制 Coroutine 的生命週期。
  2. CoroutineDispatcher:決定 Coroutine 在哪個執行緒執行。
  3. CoroutineName:給 Coroutine 取名字(方便除錯)。
  4. CoroutineExceptionHandler:處理未被捕獲的例外。

我們可以使用 + 運算子來組合這些元素:

launch(Dispatchers.IO + CoroutineName("DownloadTask")) {
    // ...
}

Job:生命週期控制

Job 是 Coroutine 的把手。當你呼叫 launch 時,它會回傳一個 Job 物件。

val job = scope.launch {
    // 執行耗時任務
}

// 在需要的時候取消它
job.cancel()
  • job.cancel():取消 Coroutine。
  • job.join():等待 Coroutine 執行結束。

父子關係

Coroutine 具有層級關係。

  • 如果你在一個 Coroutine (父) 裡面啟動另一個 Coroutine (子),子 Coroutine 會繼承父層的 Context,並且父 Job 會成為子 Job 的 parent。
  • 取消父 Job 會遞迴取消所有子 Job。
  • 父 Job 會等待所有子 Job 完成後才算完成。

Dispatchers deeply (調度器詳解)

我們在上一章簡介了 Dispatchers,這裡再詳細說明:

Dispatchers.Main

  • Android 專用。
  • 執行在 UI 主執行緒。
  • 用途:呼叫 UI 函式、更新 LiveData/State。
  • 底層:使用 Handler(Looper.getMainLooper())

Dispatchers.IO

  • 執行在共享的執行緒池 (Thread Pool)。
  • 此池會根據需求彈性擴充 (預設上限為 64 個執行緒或 CPU 核心數)。
  • 用途:阻塞性 I/O 操作 (檔案、Socket、Database)。

Dispatchers.Default

  • 執行在共享的執行緒池。
  • 池大小固定為 CPU 核心數。
  • 用途:CPU 密集型運算 (大量數據處理、圖像運算)。

Dispatchers.Unconfined

  • 這是一個特殊的調度器。它不限制執行緒,會直接在「當前的執行緒」立即執行,直到遇到第一個掛起點 (suspend point)。恢復執行時,則會在恢復的執行緒上繼續。
  • 實務上極少使用,除非你有特殊需求。

Exception Handling (例外處理)

在 Coroutine 中發生 Crash 會怎樣?

  • 如果使用 launch,例外會被視為「未捕獲例外」,預設會導致 App 崩潰。
  • 如果使用 async,例外會被封裝在 Deferred 中,直到你呼叫 .await() 時才拋出。

CoroutineExceptionHandler

我們可以使用 CoroutineExceptionHandler 來全域捕獲 launch 中的例外:

val handler = CoroutineExceptionHandler { _, exception ->
    println("Caught $exception")
}

scope.launch(handler) {
    throw RuntimeException("Oops!") // 不會導致 App 崩潰
}
注意:CoroutineExceptionHandler 只對 root coroutine (最外層) 有效。如果是子 Coroutine 拋出例外,它會委派給父 Job 處理,直到傳遞到 root。

SupervisorJob

預設情況下,如果一個子 Coroutine 失敗,它會導致父 Job 取消,進而導致所有兄弟 Coroutine 也被取消。如果這不是你想要的行為(例如:同時下載 3 張圖片,一張失敗不應影響其他兩張),可以使用 SupervisorJobsupervisorScope

val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
// 這個 scope 下的子 coroutine 失敗時,不會影響其他兄弟

小結

CoroutineContext 就像是 Coroutine 的設定檔。了解 Job 的層級關係與 Dispatcher 的調度機制,能讓你更精準地控制非同步任務的行為與穩定性。