はじめに
コルーチンは軽量スレッドのように見えますが、実際には
「コンテキスト(CoroutineContext)」という仕組みで管理されています。
コルーチンは「どのスレッドで」「どんなジョブとして」「どんなルールで」動くのか
→ それをすべて決めるのが CoroutineContext です。
1. コルーチンコンテキストとは?
CoroutineContext は、コルーチンに関する「環境設定」をまとめたものです。
次のような要素を保持しています。
| 要素 | 型 | 役割 |
|---|---|---|
Job |
Job |
キャンセルや完了状態の管理 |
Dispatcher |
CoroutineDispatcher |
実行スレッド(どこで動くか) |
CoroutineName |
CoroutineName |
デバッグ用の名前識別子 |
CoroutineExceptionHandler |
CoroutineExceptionHandler |
例外ハンドリング |
2. コンテキストの構成イメージ
これらの要素が組み合わさって「コルーチンがどのように動作するか」を決定します。
3. コンテキストの指定方法
launch / async などの起動時に、context を指定できます。
launch(Dispatchers.IO + CoroutineName("FileReader")) {
println("Running on ${Thread.currentThread().name}")
}
出力例:
Running on DefaultDispatcher-worker-1
+演算子で複数の要素を合成できます。
例:Dispatchers.IO + Job() + CoroutineName("MyTask")
4. Dispatcher の種類(どのスレッドで動くか)
| Dispatcher | 説明 | 用途例 |
|---|---|---|
Dispatchers.Default |
CPU負荷の高い処理用スレッドプール | 並列計算、ソート |
Dispatchers.IO |
I/O最適化スレッドプール | ネットワーク、DB、ファイル処理 |
Dispatchers.Main |
メインスレッド(UIスレッド) | Android の UI 更新 |
Dispatchers.Unconfined |
呼び出し元スレッドで開始(再開時は未定) | 特殊ケース(テストなど) |
例:
launch(Dispatchers.Default) { println("CPU heavy task") }
launch(Dispatchers.IO) { println("Network task") }
launch(Dispatchers.Main) { println("Update UI") }
5. Job:キャンセルと階層構造
コルーチンのキャンセル状態や完了状態を管理します。
val parent = Job()
val context = Dispatchers.Default + parent
CoroutineScope(context).launch {
println("Running...")
}
キャンセル
parent.cancel() // 子コルーチンもすべてキャンセルされる
これが「構造化並行性(Structured Concurrency)」の核です。
6. CoroutineName:デバッグのための識別名
launch(CoroutineName("Uploader")) {
println("Coroutine name: ${coroutineContext[CoroutineName]}")
}
出力:
Coroutine name: CoroutineName(Uploader)
ログ分析・デバッグ時に便利。
Thread名ではなく、論理的なタスク名として識別できます。
7. CoroutineExceptionHandler:例外処理を一元化
val handler = CoroutineExceptionHandler { _, exception ->
println("Caught: ${exception.message}")
}
CoroutineScope(Dispatchers.Default + handler).launch {
throw RuntimeException("Something went wrong!")
}
出力:
Caught: Something went wrong!
ポイント:
- 子コルーチンで未処理の例外が発生した場合、ハンドラがキャッチする。
-
asyncは例外をawait()時に再スローする(自動伝播しない点に注意)
8. コンテキストの取得と利用
現在のコルーチンのコンテキストを取得できます。
launch(Dispatchers.IO + CoroutineName("Downloader")) {
println(coroutineContext[CoroutineDispatcher]) // Dispatcher情報
println(coroutineContext[CoroutineName]) // CoroutineName
}
coroutineContext は すべての suspend 関数内で利用可能 です。
9. withContext とコンテキストの切り替え
suspend fun loadData() {
withContext(Dispatchers.IO) {
println("I/O thread: ${Thread.currentThread().name}")
}
withContext(Dispatchers.Default) {
println("CPU thread: ${Thread.currentThread().name}")
}
}
出力:
I/O thread: DefaultDispatcher-worker-2
CPU thread: DefaultDispatcher-worker-4
withContextは「コンテキストを切り替える」機能。
スレッドや Dispatcher を安全に変更できます。
10. コンテキストの継承と上書き
親スコープのコンテキストは、子コルーチンにも自動で引き継がれます。
ただし、指定した要素は上書きされます。
val scope = CoroutineScope(Dispatchers.Default + CoroutineName("Parent"))
scope.launch(CoroutineName("Child")) {
println(coroutineContext[CoroutineName]) // Child に上書きされる
}
11. 図で理解するコンテキストの構造
- Scope:コンテキストを持つ「コルーチンの枠」
- Context:構成要素を保持(Dispatcher, Job, Name, Handler)
- Coroutine:Scope + Context のもとで動く実行単位
まとめ
| 要素 | 型 | 主な役割 |
|---|---|---|
Job |
Job |
コルーチンのキャンセル・完了管理 |
Dispatcher |
CoroutineDispatcher |
実行スレッドの決定 |
CoroutineName |
CoroutineName |
論理的な識別名(デバッグ用) |
CoroutineExceptionHandler |
CoroutineExceptionHandler |
例外処理のカスタマイズ |
-
コルーチンは「コンテキスト」で全てが決まる
-
+で合成し、柔軟に制御できる -
coroutineContextは suspend 関数内でアクセス可能 -
親子のコンテキストは自動的に継承・伝播される
-
コルーチンコンテキストは「どのスレッド・どのルール・どの識別名で動作するか」を決める
-
Dispatcherでスレッドを決め、Jobでライフサイクルを制御しExceptionHandlerで安全性を担保する -
すべてのコルーチンは「Context」の上に存在する