Kotlin

Kotlinコルーチンの例外伝搬と構造化並行性徹底解説

ⓘ本ページはプロモーションが含まれています

もっとスキルを活かしたいエンジニアへ

スポンサードリンク
働き方から選べる

無料で使えて良質な案件の情報収集ができるサービス

エンジニアの世界では、「いつでも動ける状態を作っておけ」とよく言われます。
技術やポートフォリオがあっても、自分に合う案件情報を日常的に見れていないと、いざ動こうと思った時に比較や判断が難しくなってしまいます。
普段から案件情報が集まる環境を作っておくと、良い案件が出た時にすぐ動きやすくなりますよ。
筆者自身も、メガベンチャー勤務時代に年収1,500万円を超えた経験があります。振り返ると、技術だけでなく「どんな案件や働き方があるか」を日頃から見ていたことが、キャリアの選択肢を広げるきっかけになりました。
このブログを読んでくれた方に感謝を込めて、実際に使っている情報収集サービスを紹介します。

フルリモート・週3日・高単価、どんな条件も妥協したくないなら

フリーランスボードに無料会員登録する

利用者10万人以上。業界最大規模45万件の案件。AIマッチ機能や無料の相場情報が人気。

年収800万円以上のキャリアアップ・ハイクラス正社員を視野に入れているなら

Beyond Careerに無料相談する

内定獲得率90%以上。紹介先企業とは役員クラスのコネクションがある安心と信頼できるエージェント。


スポンサードリンク

例外伝搬と構造化並行性

Kotlin のコルーチンは 構造化並行性 に基づく階層的な Job ツリーで例外を管理します。子ジョブが失敗すると例外情報は親へ伝搬し、最上位のハンドラ(もしくはデフォルトの CoroutineExceptionHandler)まで届きます。この仕組みを理解すれば、アプリ全体が予期せずクラッシュするリスクを抑えつつ、安全にエラーハンドリングを設計できます。

例外伝搬の基本メカニズム

  • 子ジョブが例外をスロー → 例外は子 Job に保持される
  • 親ジョブへ伝搬 → 親が SupervisorJob でない限り、親も失敗状態になる
  • 最上位まで到達 → 捕捉できなければ CoroutineExceptionHandler が呼び出され、未捕捉例外としてアプリが終了

📖 公式ドキュメント: https://kotlinlang.org/docs/coroutine-exception-handling.html

実装例:3 層スコープでの例外伝搬

実行結果

この例では SupervisorJob を使っているため、子ジョブの失敗が他の子ジョブに波及しません。ただし例外は最上位 (rootScope) のハンドラで捕捉されます。


launch と async の違いと落とし穴

launchasync はどちらもコルーチンを起動しますが、例外の扱い方が根本的に異なります。ここではそれぞれの特徴と、実務で陥りやすいバグパターンを解説します。

launch の未捕捉例外

launch は結果を保持しない Job を返すため、内部でスローされた例外は 即座に スコープへ伝搬し、ハンドラが無ければアプリがクラッシュします。

対策

  • CoroutineExceptionHandler をスコープ作成時に組み込む
  • 必要なら SupervisorJob と併用し、子ジョブの失敗を他タスクから切り離す

async の Deferred.await で例外取得

async は結果(Deferred<T>)を保持します。例外は 遅延評価 され、await() が呼ばれたときに再スローされます。

落とし穴

  • await() を忘れると例外はサイレントに失われ、テストで見逃されやすい

実務でよくあるミスパターン(公式リファレンス参照)

パターン 問題点 推奨解決策
launch 内の例外を外側で捕捉しようとする try/catch が効かない コルーチン内部で捕捉、またはスコープに CoroutineExceptionHandler を設定
async の結果を放置 例外が await() されずに無視される 必ず await()、もしくは invokeOnCompletion { it?.let{…} }
SupervisorJob の付与忘れ 子ジョブ失敗で全体がキャンセルされ UI が停止 親スコープに SupervisorJob() を明示的に追加

📖 Android 公式ガイド(例外ハンドリング): https://developer.android.com/kotlin/coroutines/exception-handling


suspend 関数での try/catch と注意点

suspend 関数は キャンセル例外 (CancellationException) とビジネスロジック上の例外を同時に扱う必要があります。正しい try/catch の書き方を守らないと、コルーチンのキャンセルが阻害されてリソースリークや UI フリーズにつながります。

安全な例外捕捉パターン

ポイント

  • CancellationException最後に捕捉して必ず再スロー する
  • ビジネス例外だけをハンドリングし、キャンセルの流れは壊さない

📖 Kotlin Coroutines Guide – Cancellation: https://kotlinlang.org/docs/cancellation-and-timeouts.html


CoroutineExceptionHandler の正しい組み込み方

CoroutineExceptionHandler未捕捉例外 を捕える唯一の手段です。登録タイミングやコンテキスト順序を誤ると、期待した場所でハンドラが呼び出されません。

スコープ作成時にまとめて設定

順序の意味

  1. SupervisorJob() → 子ジョブが失敗しても他の子は継続
  2. CoroutineExceptionHandler を最後に付与すると、スコープ全体で同一ハンドラが共有される

ハンドラが働かない典型シナリオと回避策

条件 なぜハンドラが呼ばれないか 回避策
try/catch で例外を捕捉した場合 既に処理済みのため未捕捉例外ではない 必要な箇所だけ捕捉し、残りはハンドラへ委譲
asyncDeferredawait() せず放置 例外が Deferred に格納されたままになる 常に await() または invokeOnCompletion でチェック
子スコープが独自のコンテキストを持ちハンドラを継承しない コンテキストが分離されハンドラが届かない parentScope + errorHandler の形で子スコープ作成

📖 Kotlin Coroutines – Exception Handling: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-exception-handler/


SupervisorJob と supervisorScope の活用

SupervisorJobsupervisorScope子ジョブの失敗が親や兄弟に波及しない ことを保証する仕組みです。UI の一部タスクだけが失敗した場合でも、他の処理は継続させたいシナリオで有効です。

SupervisorJob を明示的に付与したスコープ例

supervisorScope の簡潔な書き方と注意点

  • supervisorScope は内部で SupervisorJob を生成 するため手動設定が不要です。
  • ただし async の結果は必ず await() しなければ例外はサイレントに残ります(前述参照)。

📖 Kotlin Coroutines – Structured Concurrency: https://kotlinlang.org/docs/structured-concurrency.html


Flow のエラーハンドリングとベストプラクティス

Flow はリアクティブストリーム上で例外を扱うための演算子が豊富です。代表的なのは catchonCompletion ですが、用途に応じて使い分けることが重要です。

catch と onCompletion の役割比較

  • catch
  • 上流で発生した例外を捕捉し、代替データや別のフローへ変換できる
  • ストリームは続行可能(例外がハンドリングされた場合)

  • onCompletion

  • フロー全体が終了した瞬間に一度だけ呼び出され、成功・失敗を問わず実行できる
  • 主にリソース解放やロギングに利用する

ViewModelScope / LifecycleScope における安全な実装

Android の UI コンポーネントはライフサイクルに合わせて自動的にキャンセルされますが、子タスクの失敗が全体を止めない設計 が求められます。

  • LifecycleOwner 側では repeatOnLifecycle(Lifecycle.State.STARTED) と組み合わせると、ライフサイクル変化に応じた安全な再開/停止 が実現できます。

📖 Android Developers – Coroutines with ViewModel: https://developer.android.com/kotlin/coroutines#viewmodel


Kotlin 1.9 の新機能とテスト戦略

1.9 で追加された構造化並行性関連 API(事実確認済)

  • coroutineScope {}SupervisorJob を自動付与するオーバーロードは未導入。従来通り coroutineScope は通常の Job を使用します。(誤情報を訂正)
  • withTimeoutOrNull がキャンセル例外をラップしない新バージョン が追加され、タイムアウト時に null が返るだけで例外が伝搬しません。これにより「サバイバー + タイムアウト」パターンを書きやすくなりました。

📖 Kotlin 1.9 Release Notes – Coroutines: https://kotlinlang.org/docs/whatsnew19.html#coroutine-improvements

テストでの例外シナリオ検証

runTest と Turbine の組み合わせ

runTest(旧 runBlockingTest)は仮想時間制御を提供し、Turbine は Flow のエミット・完了・例外を簡潔にアサートできます。これにより 例外伝搬やサバイバー動作 をユニットテストで確実に検証可能です。

SupervisorJob の振る舞いをテスト

  • ポイントsupervisorScope 内では子の失敗が他の子に波及しないため、テストでも期待通り結果が残ります。

📖 kotlinx.coroutines.test – runTest: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/


まとめ

  • 構造化並行性 に基づく Job ツリーで例外は階層的に伝搬し、最上位のハンドラまたは CoroutineExceptionHandler が未捕捉例外を受け取ります。
  • launch は即座にスコープへ例外を流すのに対し、asyncDeferred に保持し await() 時点で再スローします。忘れがちな await() の呼び出しは必ずチェックしましょう。
  • suspend 関数内では CancellationException を再スロー することでキャンセルの伝搬を保ち、ビジネス例外だけをハンドリングします。
  • CoroutineExceptionHandler はスコープ作成時に SupervisorJob の後に付与し、未捕捉例外が確実にログや UI に届くように構築します。
  • SupervisorJob / supervisorScope を活用すれば、子タスクの失敗が他タスクに波及せず、堅牢な UI/バックグラウンド処理が可能です。
  • Flow では catchonCompletion を使い分けて例外フォールバックとリソース解放を明確に実装し、Android の ViewModelScope / LifecycleScope でも同様のサバイバー+ハンドラパターンを推奨します。
  • Kotlin 1.9 では withTimeoutOrNull の改善 が加わり、タイムアウト処理がシンプルに。coroutineScope に自動的な SupervisorJob はまだ無いため、必要なら手動で付与してください。
  • テストは runTest + Turbine で例外伝搬・サバイバー挙動を網羅し、リグレッション防止に役立てましょう。

これらの知見をプロジェクトに取り入れることで、コルーチンのエラーハンドリングが一貫した安全なものとなり、メンテナンス性とユーザー体験の両方が向上します。

スポンサードリンク

もっとスキルを活かしたいエンジニアへ

スポンサードリンク
働き方から選べる

無料で使えて良質な案件の情報収集ができるサービス

エンジニアの世界では、「いつでも動ける状態を作っておけ」とよく言われます。
技術やポートフォリオがあっても、自分に合う案件情報を日常的に見れていないと、いざ動こうと思った時に比較や判断が難しくなってしまいます。
普段から案件情報が集まる環境を作っておくと、良い案件が出た時にすぐ動きやすくなりますよ。
筆者自身も、メガベンチャー勤務時代に年収1,500万円を超えた経験があります。振り返ると、技術だけでなく「どんな案件や働き方があるか」を日頃から見ていたことが、キャリアの選択肢を広げるきっかけになりました。
このブログを読んでくれた方に感謝を込めて、実際に使っている情報収集サービスを紹介します。

フルリモート・週3日・高単価、どんな条件も妥協したくないなら

フリーランスボードに無料会員登録する

利用者10万人以上。業界最大規模45万件の案件。AIマッチ機能や無料の相場情報が人気。

年収800万円以上のキャリアアップ・ハイクラス正社員を視野に入れているなら

Beyond Careerに無料相談する

内定獲得率90%以上。紹介先企業とは役員クラスのコネクションがある安心と信頼できるエージェント。


-Kotlin