コルーチン階層におけるキャンセルの伝播
親子関係にあるコルーチン間でキャンセルがどのように伝播するかを理解します。
「コルーチン階層におけるキャンセルの伝播」は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フィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- 協調的キャンセル:isActiveとensureActive
- withTimeoutとwithTimeoutOrNull
- finallyとNonCancellableによるクリーンアップ
- コルーチン階層におけるキャンセルの伝播