0Pricing
Kotlin Academy · レッスン

SupervisorJobとJob:失敗の分離

SupervisorJobを使い、子コルーチンの1つの失敗で兄弟コルーチンがキャンセルされないようにします。

「SupervisorJobとJob:失敗の分離」はCoddyKit上の無料Kotlin Academyレッスンです。 これはレッスン1/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはKotlin Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Kotlin Academyコースには全4レッスンが含まれています。

デフォルトのJob失敗伝播

通常のJobでは、子が失敗すると親がキャンセルされ、親によってすべての兄弟もキャンセルされます。1つの失敗で階層全体が停止します。

import kotlinx.coroutines.*
fun main() = runBlocking {
    try {
        coroutineScope {
            launch { delay(50); throw RuntimeException("child 1 failed") }
            launch { delay(1000); println("child 2 — never prints") }
        }
    } catch (e: RuntimeException) {
        println("Caught: ${e.message}")
    }
}

SupervisorJobの概要

SupervisorJobではルールが変わり、子の失敗によって兄弟や親がキャンセルされることはありません。各子は独立して失敗します。

import kotlinx.coroutines.*
fun main() = runBlocking {
    val supervisor = SupervisorJob()
    val scope = CoroutineScope(coroutineContext + supervisor)
    scope.launch { throw RuntimeException("child 1 failed") }
    scope.launch { delay(100); println("child 2 still runs") }
    delay(200)
    supervisor.cancel()
}

supervisorScopeビルダー

supervisorScope { }は、親としてSupervisorJobを持つスコープを作成します。独立した子を実行するための慣用的な方法です。

import kotlinx.coroutines.*
fun main() = runBlocking {
    supervisorScope {
        val job1 = launch {
            throw RuntimeException("job1 failed")
        }
        val job2 = launch {
            delay(100)
            println("job2 succeeded")
        }
        job1.join()  // wait for job1 (it failed)
        job2.join()  // job2 is unaffected
    }
}

子の失敗を捕捉する

supervisorScopeでは、各子が自身の例外を処理します。子で処理されない例外は、子ごとに捕捉する必要があります。

import kotlinx.coroutines.*
fun main() = runBlocking {
    supervisorScope {
        val result = async {
            delay(50)
            throw RuntimeException("async failed")
        }
        try {
            result.await()
        } catch (e: RuntimeException) {
            println("Caught from async: ${e.message}")
        }
        println("Scope continues")
    }
}

スコープ作成時のJobとSupervisorJob

個々の子の失敗後も存続させたいサービスやViewModelのCoroutineScopeを作成するときは、SupervisorJob()を渡します。

import kotlinx.coroutines.*
class MyService {
    private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
    fun startTask(name: String) {
        scope.launch {
            if (name == "fail") throw RuntimeException("$name failed")
            println("$name done")
        }
    }
    fun stop() = scope.cancel()
}
fun main() = runBlocking {
    val svc = MyService()
    svc.startTask("fail")
    svc.startTask("ok")
    delay(100)
    svc.stop()
}

viewModelScopeはSupervisorJobを使用

AndroidのviewModelScopeはSupervisorJobを基盤としています。1つのlaunchが失敗しても、ViewModelの他のコルーチンはキャンセルされません。

import kotlinx.coroutines.*
// Android ViewModel internals:
// val viewModelScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
// Each launch is independent — one failure doesn't crash the ViewModel
fun main() = runBlocking {
    println("viewModelScope = SupervisorJob + Main")
}

supervisorScopeによる並列処理の分割

一部のタスクが失敗しても他のタスクに影響を与えずに並列実行する場合は、supervisorScope + async を使用します。

import kotlinx.coroutines.*
fun main() = runBlocking {
    val results = supervisorScope {
        val a = async { delay(50); "result-A" }
        val b = async { throw RuntimeException("B failed") }
        val c = async { delay(30); "result-C" }
        listOf(
            runCatching { a.await() },
            runCatching { b.await() },
            runCatching { c.await() }
        )
    }
    results.forEach { println(it) }
}

通常の Job を使用するタイミング

すべての子が一緒に成功する必要がある場合は、通常の Job(coroutineScope)を使用します。1つの失敗でグループ全体を中止します。トランザクションのように、すべて成功するか、すべて失敗するかのどちらかです。

import kotlinx.coroutines.*
fun main() = runBlocking {
    try {
        coroutineScope {  // regular Job
            val a = async { delay(50); "A" }
            val b = async { throw RuntimeException("B failed") }
            println(a.await())
            println(b.await())  // throws — cancels a too
        }
    } catch (e: RuntimeException) {
        println("Transaction failed: ${e.message}")
    }
}

supervisorScope での例外の伝播

supervisorScope では、子のトップレベルから例外が外に伝播した場合、つまり子の中に try-catch がない場合に限り、例外がスコープに伝播します。親には伝播しません。

import kotlinx.coroutines.*
fun main() = runBlocking {
    supervisorScope {
        // Exception caught inside child — scope unaffected
        launch {
            try { throw RuntimeException("handled") }
            catch (e: Exception) { println("Child caught: ${e.message}") }
        }
        delay(100)
        println("Scope survived")
    }
}

SupervisorJob での CoroutineExceptionHandler

スーパーバイザースコープに CoroutineExceptionHandler を設定すると、キャッチされなかった子の失敗を記録したり、その失敗に対応したりできます。スコープがクラッシュすることもありません。

import kotlinx.coroutines.*
fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { _, e ->
        println("Uncaught: ${e.message}")
    }
    val scope = CoroutineScope(SupervisorJob() + handler)
    scope.launch { throw RuntimeException("unhandled failure") }
    scope.launch { delay(100); println("still running") }
    delay(200)
    scope.cancel()
}

失敗の分離のまとめ

Job:1つの子が失敗すると、すべてが失敗します。SupervisorJob:子はそれぞれ独立して失敗します。サービスや ViewModel には supervisor を、トランザクションのようなグループには通常の Job を使用します。

import kotlinx.coroutines.*
// Decision table:
// coroutineScope { }   -> Job: all-or-nothing
// supervisorScope { }  -> SupervisorJob: independent failures
// CoroutineScope(SupervisorJob()) -> long-lived service
fun main() = runBlocking { println("Pick the right job for the right scope") }

クイックチェック

子の失敗によって兄弟がキャンセルされるのを防ぐスコープはどれですか。

振り返り

Job:失敗が兄弟に連鎖します。SupervisorJob/supervisorScope:失敗は子ごとに分離されます。独立したタスクを並列実行する場合は supervisorScope を、トランザクションのようなグループには通常の coroutineScope を使用します。

よくある質問

「SupervisorJobとJob:失敗の分離」レッスンは無料ですか?

はい。「SupervisorJobとJob:失敗の分離」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Kotlin Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Kotlin Academyコースには全4レッスンが含まれています。

「SupervisorJobとJob:失敗の分離」で何を学びますか?

SupervisorJobを使い、子コルーチンの1つの失敗で兄弟コルーチンがキャンセルされないようにします。 ブラウザで直接実行するハンズオンコードでKotlin Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Kotlin Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのKotlin Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン1/4です。

「SupervisorJobとJob:失敗の分離」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このKotlin Academyレッスンでコードを書いて実行できますか?

はい。すべてのKotlin Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. SupervisorJobとJob:失敗の分離
  2. CoroutineExceptionHandler:グローバルな未捕捉例外ハンドラー
  3. async/awaitによる例外の伝播
  4. 堅牢なコルーチンアーキテクチャの設計
← Kotlin Academyに戻る