0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Kotlin】Kotlin CoroutinesにおけるエラーハンドリングとSupervisor設計

Posted at

はじめに

― 「1つが失敗しても、全体を止めない並行処理」 ―


1. コルーチンのエラーハンドリングとは?

Kotlinのコルーチンでは、例外の扱い方がスレッドモデルと異なります
例外はコルーチンのスコープ構造を通じて伝播します。

suspend fun taskA() {
    throw Exception("Aで失敗!")
}

fun main() = runBlocking {
    try {
        launch { taskA() }
    } catch (e: Exception) {
        println("キャッチ: ${e.message}")
    }
}

結果:

Exception in thread "main" java.lang.Exception: Aで失敗!

launch 内の例外は 外側の try/catch では拾えません
launch は「非同期で動く別スコープ」だからです。


2. asynclaunch の違い

関数 戻り値 例外発生時の挙動 主な用途
launch Job 直ちに親に伝播 Fire-and-forget(実行だけ)
async Deferred<T> await()時にスロー 値を返す処理

例:

fun main() = runBlocking {
    val job = launch {
        throw Exception("launch例外")
    }

    val deferred = async {
        throw Exception("async例外")
    }

    job.join() // ここではキャッチできない
    deferred.await() // ここで初めて例外が発生
}

3. 構造化並行性と例外伝播の関係

coroutineScope の場合

suspend fun scopeExample() = coroutineScope {
    launch { delay(500); println("B 完了") }
    launch { throw Exception("A 失敗") }
}

出力:

Exception: A 失敗

一つの子コルーチンが失敗すると、
スコープ全体がキャンセルされる(他の子も停止)。


4. supervisorScope で独立させる

suspend fun supervisorExample() = supervisorScope {
    launch {
        throw Exception("A 失敗")
    }
    launch {
        delay(500)
        println("B 完了(継続)")
    }
}

出力:

Exception: A 失敗
B 完了(継続)

supervisorScope他の子タスクに影響を与えません。
→ 複数の独立タスクを安全に並行実行できます。


5. SupervisorJob でスコープ全体を監督化

アプリ全体のルートスコープを「Supervisor」に変えることで、
個別の失敗を隔離できます。

val supervisorScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)

fun main() {
    supervisorScope.launch {
        launch {
            throw Exception("A 失敗")
        }
        launch {
            delay(500)
            println("B 継続")
        }
    }
    Thread.sleep(1000)
}

出力:

Exception: A 失敗
B 継続

SupervisorJob は「スコープの失敗耐性」を提供します。


6. CoroutineExceptionHandler でグローバル捕捉

例外が伝播する前に、
CoroutineExceptionHandler を使ってログ・通知処理を挟めます。

val handler = CoroutineExceptionHandler { _, e ->
    println("例外捕捉: ${e.message}")
}

fun main() = runBlocking {
    launch(handler) {
        throw Exception("ネットワークエラー")
    }
}

出力:

例外捕捉: ネットワークエラー

async の例外は await() 時に発生するため、
CoroutineExceptionHandler では捕捉されません。


7. 典型的なエラーハンドリング設計パターン

パターン①:すべて失敗時に停止(同期的)

suspend fun strictLoad() = coroutineScope {
    val user = async { fetchUser() }
    val orders = async { fetchOrders() }
    println("${user.await()}, ${orders.await()}")
}

→ どちらかが失敗したら全体キャンセル。


パターン②:部分失敗を許容(非同期的)

suspend fun tolerantLoad() = supervisorScope {
    val user = async { runCatching { fetchUser() }.getOrElse { "User失敗" } }
    val orders = async { runCatching { fetchOrders() }.getOrElse { "Order失敗" } }
    println("${user.await()}, ${orders.await()}")
}

出力例:

User失敗, 注文リスト

runCatching {} を組み合わせると、
タスク単位で例外を吸収して継続可能です。


パターン③:ViewModelやRepository層でのSupervisor設計

class MyViewModel : ViewModel() {
    private val scope = viewModelScope + SupervisorJob()

    fun loadAll() {
        scope.launch {
            launch { fetchUser() }
            launch { fetchOrders() }
            launch { fetchRecommendations() }
        }
    }
}

Androidでは viewModelScope が自動キャンセルを担い、
SupervisorJob が安全な並行性を保証します。


8. 実践Tips(よくある落とし穴)

問題 原因 解決策
例外が親に伝わらない async の中で未await() await()で例外を発火させる
すべての子が止まる 通常のcoroutineScopeを使用 supervisorScopeに変更
try/catchで拾えない launch内の例外 CoroutineExceptionHandlerを使用
グローバルリーク GlobalScope利用 スコープを限定する(ViewModelScopeなど)

まとめ

要点 内容
launch 例外即伝播(値を返さない)
async await()時に例外発生(値を返す)
coroutineScope 子の失敗で全体停止
supervisorScope / SupervisorJob 部分失敗を許容して継続
CoroutineExceptionHandler グローバル例外捕捉
ベストプラクティス スコープ境界を明確化+局所エラーハンドリング

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?