Kotlin

Kotlin Coroutines の例外伝搬とキャンセル完全ガイド – 実務で使えるベストプラクティス

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

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

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

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

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

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

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

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

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

Beyond Careerに無料相談する

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


スポンサードリンク

1. launch と async の例外伝搬の違い

launchasync はどちらもコルーチンビルダーですが、失敗時の振る舞いが根本的に異なります。このセクションではそれぞれの特徴と、実際に例外がいつ・どこで再スローされるかを確認します。

1‑1. 基本的な挙動

launchfire‑and‑forget 用に設計されており、内部で未捕捉例外が発生すると即座に親スコープへ伝搬し、スコープ全体がキャンセルされます。一方 async は結果を Deferred<T> に包むため、例外は 保留 され、呼び出し側が await()(または join()) を行ったときに初めて再スローされます。

公式参照: Kotlin Coroutines – Structured concurrency

1‑2. 実務上の使い分け指針

ビルダー 主な用途 例外処理のポイント
launch UI 更新、I/O の副作用系 未捕捉例外はハンドラか SupervisorJob で吸収
async 計算結果や API 呼び出しの戻り値 必ず await()/join() で例外を取得

2. 子コルーチンから親へ例外が伝搬する仕組み

子コルーチンが未捕捉例外を投げたとき、デフォルトの Job ではその例外が親スコープ全体に波及し、すべての子がキャンセルされます。逆に SupervisorJob を利用すると、失敗した子だけが止まり他は継続できます。

2‑1. デフォルト Job の伝搬

上記は SupervisorJob を付与しているため、子①の例外が子②に影響しません。Job()(デフォルト)を使うと子①の失敗で子②もキャンセルされます。

公式参照: CoroutineExceptionHandler と SupervisorJob

2‑2. 部分的失敗を許容する設計パターン

  • SupervisorJob をスコープ全体に付与 → 長期タスクで部分失敗を許容したいとき
  • supervisorScope { … } → 短命な並列処理だけをサンドボックス化したいとき

両者とも「例外は孤立させ、他の子コルーチンは続行」する点が共通です。


3. CoroutineExceptionHandler の正しい配置

CoroutineExceptionHandler未捕捉例外 を一元的にロギングしたり UI に通知したりするために使います。ただし、ハンドラが受け取れるのは「スコープで捕捉されていない」例外だけです。

3‑1. 設置場所と適用範囲

viewModelScope にハンドラを合成すれば、ViewModel が生存している間はすべての子コルーチンで未捕捉例外がロギングされます。

公式参照: CoroutineExceptionHandler

3‑2. async の例外はハンドラだけで捕捉できない

async の結果を 必ず await() もしくは join() して例外を取得しなければ、ハンドラだけでは検知できません。


4. SupervisorJob と supervisorScope の実装パターン

4‑1. 長期スコープでの SupervisorJob

このパターンは ネットワーク呼び出しを並列実行 したいときに便利です。どちらかが失敗しても UI が完全に止まることはありません。

4‑2. 短命ブロックでの supervisorScope

supervisorScopeローカルなサンドボックス として機能し、外部スコープへの影響を最小限に抑えます。

公式参照: SupervisorJob と supervisorScope


5. CancellationException の取り扱い

CancellationException はコルーチンの協調キャンセルを表す特別な例外です。捕捉したら必ず再スローしないと、キャンセル状態が失われてリソースリークにつながります。

5‑1. 再スローが必要な理由

catch ブロックでログやクリーンアップだけを行い、例外自体は捨てないことが重要です。

5‑2. キャンセルの双方向伝搬

条件 子 → 親へのキャンセル 親 → 子へのキャンセル
Job.cancel() 呼び出し 即時 CancellationException が上位へ伝搬 すべての子が即座にキャンセル
子コルーチンが例外を投げる(デフォルト Job 親もキャンセルされる -
SupervisorJob 使用時 子例外は親に波及しない 明示的に cancel() したときだけ全体がキャンセル

公式参照: Cancellation and Timeouts


6. 例外ハンドリングのベストプラクティス(Android 向け)

6‑1. try / catch の粒度

例外捕捉は ビジネスロジック単位、もしくは withContext 内に限定し、広範囲で捕捉するとスタックトレースが失われます。

6‑2. viewModelScope とハンドラの併用

viewModelScope が自動的にキャンセルされるため、UI が破棄されたときのリークを防げます。

6‑3. repeatOnLifecycle で安全なデータ収集

repeatOnLifecycleライフサイクルが STARTED である間だけ コレクションを実行し、バックグラウンドになると自動的にキャンセルします。これにより例外も安全に処理できます。

公式参照: repeatOnLifecycle


7. テストで例外シナリオを検証する

7‑1. runTest を用いた決定的テスト

runTest は仮想タイムを使用するため、delay などの非同期処理が即座に完了し、例外伝搬ロジックを deterministic に確認できます。

公式参照: Testing coroutines

7‑2. SupervisorJob のテストポイント


8. Kotlin 1.9 と Coroutines 1.8 の新機能(正確な情報)

新機能 説明 実務での活用例
sealed interface(Kotlin 1.9) sealed がインターフェースでも使えるようになり、エラーハンドリング用の型階層を簡潔に表現できる。 API の結果を Result<Success, Failure> として安全に扱う
structured concurrency の内部改善(Coroutines 1.8) coroutineScope/supervisorScope の実装が軽量化され、コンパイル時チェックが強化された。新しい API は追加されていないため、既存の coroutineScopesupervisorScope をそのまま利用できる。 以前と同様に安全なスコープを作成しつつ、ビルドエラーで誤用を防止
Dispatchers.Main.immediate のデフォルト改善 UI スレッド上の即時実行が保証され、repeatOnLifecycle と相性が向上した。 UI 更新処理の遅延バグが減少し、フラッシュな描画が可能

公式参照: Kotlin 1.9 Release Notes ・ kotlinx.coroutines 1.8 Release Notes


まとめ

  • launchasync の例外伝搬は根本的に異なる。前者は即座にスコープ全体をキャンセルし、後者は await() 時点で再スローされるので必ず取得すること。
  • CoroutineExceptionHandler はトップレベルスコープ(例: viewModelScope)へ合成 すれば未捕捉例外の一元ロギングが実現でき、async の保留例外は個別にハンドリングする必要がある。
  • 部分失敗を許容したい場合は SupervisorJob または supervisorScope を活用し、子コルーチンの失敗が他に波及しない設計を取る。
  • CancellationException は必ず再スロー してキャンセル情報を上位へ伝搬させ、リソースリークを防止する。
  • Android のライフサイクルと組み合わせた安全なコルーチン管理viewModelScope+ハンドラ、repeatOnLifecycle)で UI の安定性を確保できる。
  • テストは runTestassertThrows で決定的に実施し、例外シナリオが正しく伝搬することを検証すべき。
  • Kotlin 1.9 / Coroutines 1.8 の最新機能は型安全とスコープ安全性をさらに高めるので、プロジェクトの依存バージョン更新を検討する価値がある。

これらのポイントをコードベースに組み込むことで、非同期処理の堅牢性が大幅に向上し、予期せぬクラッシュやリソースリークのリスクを最小限に抑えることができます。ぜひ実務で試してみてください。

スポンサードリンク

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

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

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

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

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

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

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

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

Beyond Careerに無料相談する

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


-Kotlin