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で理解するキャンセル処理

Last updated at Posted at 2025-10-16

はじめに

Kotlin のコルーチンでは、キャンセル(Cancellation) は非常に重要な概念です。
非同期処理を中断し、リソースを安全に解放することで、アプリ全体の安定性を確保します。

この記事では以下の内容を体系的に紹介します:

  • コルーチンキャンセルの基本構造
  • Job とキャンセル伝播の仕組み
  • isActive / ensureActive() の使い方
  • withTimeout() / withTimeoutOrNull()
  • try-finallyNonCancellable による後処理
  • SupervisorScope との関係

1. コルーチンキャンセルの基本

KotlinのCoroutineScope.launchで起動されたコルーチンは、Jobオブジェクトを返します。
Jobを通じてキャンセル制御が行われます。

import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        repeat(1000) { i ->
            println("Job: I'm working $i ...")
            delay(500L)
        }
    }

    delay(1300L)
    println("Main: Tired of waiting! Cancelling...")
    job.cancel()              // キャンセル要求
    job.join()                // 終了待機
    println("Main: Done.")
}

ポイント:

  • cancel()キャンセルリクエストを送るだけ
  • コルーチンは協調的に(cooperative)キャンセルされる
  • delay()yield() などの中断可能関数(suspending function) がキャンセルを検知して停止する

2. 協調的キャンセルの仕組み

すべてのコルーチンが即時に停止するわけではありません。
CPU計算のように delay() を使わない処理では、自らキャンセル状態をチェックする必要があります。

fun main() = runBlocking {
    val job = launch(Dispatchers.Default) {
        var i = 0
        while (isActive) { // ← 協調的キャンセル
            println("Working ${i++} ...")
        }
    }

    delay(100L)
    println("Cancel!")
    job.cancelAndJoin()
}

isActive

  • CoroutineScope の拡張プロパティ
  • 現在のコルーチンがアクティブかどうかを表す

代わりに ensureActive() を使うと、キャンセル状態なら CancellationException を即時投げます。


3. タイムアウトによるキャンセル

withTimeout

Kotlin には一定時間後に自動キャンセルする仕組みも用意されています。

import kotlinx.coroutines.*

fun main() = runBlocking {
    try {
        withTimeout(1300L) {
            repeat(1000) { i ->
                println("Job: $i ...")
                delay(500L)
            }
        }
    } catch (e: TimeoutCancellationException) {
        println("Timed out!")
    }
}

withTimeoutOrNull

例外を発生させず、タイムアウト時に null を返します。

val result = withTimeoutOrNull(1000L) {
    delay(1500L)
    "Finished"
}
println(result) // => null

4. 後処理とキャンセル不可領域

キャンセル時にも必ず後処理が必要な場合は、try-finally を使います。

val job = launch {
    try {
        repeat(1000) { i ->
            println("Job: $i ...")
            delay(500L)
        }
    } finally {
        println("Job: Cleaning up resources...")
    }
}

ただし、finally で呼ばれる処理もキャンセル可能です。
確実に実行したい場合は、withContext(NonCancellable) を使用します。

finally {
    withContext(NonCancellable) {
        println("Running cleanup even if cancelled...")
        delay(1000L)
        println("Cleanup finished.")
    }
}

5. キャンセル伝播と SupervisorScope

構造化並行性により、親スコープがキャンセルされると子コルーチンも連鎖的にキャンセルされます。

fun main() = runBlocking {
    val parent = launch {
        launch {
            try {
                repeat(1000) { i ->
                    println("Child: $i ...")
                    delay(500L)
                }
            } finally {
                println("Child: cancelled.")
            }
        }
    }

    delay(1300L)
    println("Parent cancelling...")
    parent.cancelAndJoin()
    println("Done.")
}

ただし、SupervisorScope を使うと子の失敗が親へ伝播しません

supervisorScope {
    launch {
        throw RuntimeException("Failure in child")
    }
    launch {
        delay(1000)
        println("Another child still running")
    }
}

まとめ

概念 説明
cancel() キャンセル要求を送る(協調的)
isActive / ensureActive() キャンセル状態を明示的に確認
withTimeout() タイムアウト自動キャンセル
try-finally リソース解放
NonCancellable キャンセル不可の後処理領域
SupervisorScope 子の失敗が親へ伝播しない

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?