0Pricing
Kotlin Academy · レッスン

コルーチン階層におけるキャンセルの伝播

親子関係にあるコルーチン間でキャンセルがどのように伝播するかを理解します。

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

親子関係

コルーチンが launch で別のコルーチンを起動すると、子は親の Job に参加します。親をキャンセルすると、すべての子もキャンセルされます。

import kotlinx.coroutines.*
fun main() = runBlocking {
    val parent = launch {
        launch { delay(1000); println("child 1") }
        launch { delay(1000); println("child 2") }
        delay(1000)
        println("parent")
    }
    delay(50)
    parent.cancel()
    parent.join()
    println("All cancelled")
}

子の失敗による親のキャンセル

子がキャンセル以外の例外をスローすると、デフォルトの Job の動作により、親とすべての兄弟コルーチンがキャンセルされます。

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

キャンセルは上方向には伝播しない

子のキャンセルによって親がキャンセルされることはありません。親に伝播するのは未処理の例外だけです。親は必要に応じて子をキャンセルできます。

import kotlinx.coroutines.*
fun main() = runBlocking {
    val parent = launch {
        val child = launch {
            delay(1000)
            println("child done")
        }
        delay(50)
        child.cancel()  // cancels child
        child.join()
        println("parent still running")  // parent is fine
    }
    parent.join()
}

coroutineScope と GlobalScope の違い

coroutineScope は子スコープを作成し、キャンセルとエラーが伝播します。GlobalScope は親を持たない孤立したコルーチンを作成するため、本番環境では避けてください。

import kotlinx.coroutines.*
fun main() = runBlocking {
    try {
        coroutineScope {
            launch { delay(50); throw RuntimeException("error") }
        }
    } catch (e: Exception) {
        println("coroutineScope propagated: ${e.message}")
    }
    // GlobalScope.launch { } would NOT propagate to runBlocking
}

サブツリーのキャンセル

すべての launch または async は Job を返します。ジョブをキャンセルすると、ネストされた launch を含むサブツリー全体がキャンセルされます。

import kotlinx.coroutines.*
fun main() = runBlocking {
    val root = launch {
        launch {
            launch { delay(1000); println("deep") }
            delay(1000)
        }
    }
    delay(50)
    root.cancel()
    root.join()
    println("Whole subtree cancelled")
}

cancel() 後の join()

処理を続行する前にコルーチンとその子が完全に停止するのを待つため、必ず cancel() の後に join() を呼び出してください。

import kotlinx.coroutines.*
fun main() = runBlocking {
    val job = launch {
        try { delay(1000) }
        finally { println("cleanup ran") }
    }
    delay(50)
    job.cancel()
    job.join()  // waits for finally to complete
    println("Proceeded after join")
}

cancelAndJoin()

job.cancelAndJoin() は、キャンセルしてから join する便利な関数で、cancel() + join() と同等です。

import kotlinx.coroutines.*
fun main() = runBlocking {
    val job = launch {
        try { delay(1000) }
        finally { println("cleanup") }
    }
    delay(50)
    job.cancelAndJoin()  // cancel + join in one call
    println("Done")
}

async を通じた伝播

async では、例外は Deferred に保存され、await() を呼び出したときにスローされます。キャッチされなければ、親にも伝播します。

import kotlinx.coroutines.*
fun main() = runBlocking {
    try {
        coroutineScope {
            val d = async { throw RuntimeException("async fail") }
            d.await()  // rethrows here
        }
    } catch (e: RuntimeException) {
        println("Caught from async: ${e.message}")
    }
}

構造化された並行性の保証

構造化された並行性により、スコープが終了するときには、そのすべての子も終了していることが保証されます。コルーチンがリークすることはなく、完了するかキャンセルされます。

import kotlinx.coroutines.*
suspend fun doWork() = coroutineScope {
    launch { delay(100); println("work 1") }
    launch { delay(200); println("work 2") }
    // both children complete before doWork returns
}
fun main() = runBlocking {
    doWork()
    println("All work done")
}

キャンセルの伝播図

階層は次のとおりです。親のキャンセル → すべての子がキャンセル。子のキャンセル → そのサブツリーのみ。子の例外 → 親がキャンセル → 兄弟がキャンセル。

import kotlinx.coroutines.*
fun main() = runBlocking {
    // Parent
    launch {
        val c1 = launch { delay(1000); println("c1") }  // child 1
        val c2 = launch { delay(1000); println("c2") }  // child 2
        delay(50)
        c1.cancel()  // only c1 cancelled
        c1.join()
        println("c2 still active: ${c2.isActive}")
        c2.cancelAndJoin()
    }.join()
}

Android における CoroutineScope のライフサイクル

Android では、ViewModel がクリアされると viewModelScope がキャンセルされます。起動したすべてのコルーチンが自動的にキャンセルされるため、手動でクリーンアップする必要はありません。

import kotlinx.coroutines.*
// Pseudocode:
// class MyViewModel : ViewModel() {
//     fun load() = viewModelScope.launch {
//         val data = repo.fetch() // cancelled if VM cleared
//         _state.value = data
//     }
// }
fun main() = runBlocking { println("viewModelScope cancels on ViewModel.onCleared()") }

確認問題

通常の Job の下で子が未処理の例外をスローした場合、兄弟コルーチンはどうなりますか。

まとめ

親のキャンセルはすべての子に連鎖します。子の失敗は親と兄弟に伝播します(通常の Job の場合)。安全に終了処理を行うには cancelAndJoin() を使ってください。構造化された並行性により、コルーチンのリークが防止されます。

よくある質問

「コルーチン階層におけるキャンセルの伝播」レッスンは無料ですか?

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

「コルーチン階層におけるキャンセルの伝播」で何を学びますか?

親子関係にあるコルーチン間でキャンセルがどのように伝播するかを理解します。 ブラウザで直接実行するハンズオンコードでKotlin Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

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

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

「コルーチン階層におけるキャンセルの伝播」レッスンにはどのくらい時間がかかりますか?

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

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

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

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

  1. 協調的キャンセル:isActiveとensureActive
  2. withTimeoutとwithTimeoutOrNull
  3. finallyとNonCancellableによるクリーンアップ
  4. コルーチン階層におけるキャンセルの伝播
← Kotlin Academyに戻る