Kotlin Academy · レッスン

finallyとNonCancellableによるクリーンアップ

finallyとwithContext(NonCancellable)を使い、キャンセルされたコルーチンでリソースを解放します。

レッスン 3/413 ステップ

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

キャンセルと finally

コルーチンがキャンセルされると、サスペンドポイントは CancellationException をスローします。finally ブロック内のコードは必ず実行されます。ただし、finally 自体はキャンセル済みのコルーチンコンテキストで実行されます。

import kotlinx.coroutines.*
fun main() = runBlocking {
    val job = launch {
        try {
            delay(1000)
        } finally {
            println("finally: cleaning up")
        }
    }
    delay(50)
    job.cancel()
    job.join()
}

問題: finally 内でのサスペンド

キャンセルされたコルーチンの finally 内で delay や別のサスペンド処理を実行しようとすると、コンテキストがすでにキャンセルされているため、即座に例外がスローされます。

import kotlinx.coroutines.*
fun main() = runBlocking {
    val job = launch {
        try {
            delay(1000)
        } finally {
            // delay(100) // throws CancellationException here!
            println("Sync cleanup OK; async cleanup needs NonCancellable")
        }
    }
    delay(50); job.cancel(); job.join()
}

withContext(NonCancellable)

finally 内のサスペンド呼び出しを withContext(NonCancellable) でラップすると、親がキャンセルされた後でもキャンセルされないコンテキストで実行できます。

import kotlinx.coroutines.*
fun main() = runBlocking {
    val job = launch {
        try {
            delay(1000)
        } finally {
            withContext(NonCancellable) {
                delay(100)  // OK: runs despite cancellation
                println("Async cleanup complete")
            }
        }
    }
    delay(50); job.cancel(); job.join()
}

NonCancellable を使う場面

NonCancellable は、DB 接続のクローズ、ログのフラッシュ、分析イベントの送信など、キャンセルに関係なく完了させる必要がある非同期クリーンアップに使います。

import kotlinx.coroutines.*
suspend fun closeConnection() {
    println("Closing DB connection...")
    delay(50) // simulate async close
    println("Connection closed")
}
fun main() = runBlocking {
    val job = launch {
        try { delay(1000) }
        finally { withContext(NonCancellable) { closeConnection() } }
    }
    delay(50); job.cancel(); job.join()
}

NonCancellable はスコープではない

NonCancellable は withContext に渡せる Job です。コルーチンスコープとして直接使用せず、finally でのクリーンアップにのみ使用してください。

import kotlinx.coroutines.*
fun main() = runBlocking {
    // WRONG usage:
    // launch(NonCancellable) { ... } // launches a coroutine that cannot be cancelled
    // RIGHT usage:
    launch {
        try { delay(500) }
        finally {
            withContext(NonCancellable) {
                println("Safe cleanup")
            }
        }
    }.also { delay(50); it.cancel(); it.join() }
}

複数のリソースのクリーンアップ

複数のクリーンアップ処理を、1つの withContext(NonCancellable) ブロック内に連続して記述してください。

import kotlinx.coroutines.*
fun main() = runBlocking {
    launch {
        try {
            delay(1000)
        } finally {
            withContext(NonCancellable) {
                delay(20); println("Step 1: flush cache")
                delay(20); println("Step 2: close socket")
                delay(20); println("Step 3: log audit")
            }
        }
    }.also { delay(50); it.cancel(); it.join() }
}

try/catch/finally パターン

ビジネスエラーには try-catch を使い、クリーンアップには finally を使います。これが標準的な構造化パターンです。

import kotlinx.coroutines.*
fun main() = runBlocking {
    launch {
        try {
            delay(100)
            throw RuntimeException("business error")
        } catch (e: RuntimeException) {
            println("Handling: ${e.message}")
        } finally {
            withContext(NonCancellable) {
                println("Always cleanup")
            }
        }
    }.join()
}

クリーンアップの順序

finally ブロックはネストの逆順に実行されます。通常の Java/Kotlin コードと同様に、外側の try-finally は内側のブロックの後に実行されます。

import kotlinx.coroutines.*
fun main() = runBlocking {
    launch {
        try {
            try {
                delay(1000)
            } finally { println("Inner cleanup") }
        } finally { println("Outer cleanup") }
    }.also { delay(50); it.cancel(); it.join() }
}

AutoCloseable リソースに対する use()

AutoCloseable リソースには、Kotlin の use { } 拡張関数を使います。例外やキャンセルが発生した場合でも、自動的に close() が呼び出されます。

import kotlinx.coroutines.*
class Connection : AutoCloseable {
    init { println("Opened") }
    override fun close() { println("Closed") }
    suspend fun query(): String { delay(50); return "result" }
}
fun main() = runBlocking {
    val result = Connection().use { it.query() }
    println(result)
}

キャンセル理由のログ出力

catch または finally で CancellationException からキャンセルメッセージを取得し、コルーチンが停止した理由をログに記録できます。

import kotlinx.coroutines.*
fun main() = runBlocking {
    val job = launch {
        try {
            delay(1000)
        } catch (e: CancellationException) {
            println("Cancelled: ${e.message}")
            throw e
        }
    }
    delay(50)
    job.cancel("user navigated away")
    job.join()
}

invokeOnCompletion による CoroutineScope のクリーンアップ

Job.invokeOnCompletion は、ジョブがどのような理由で終了した場合でも実行されるコールバックを登録します。親レベルのクリーンアップでは、finally の代わりに使用できます。

import kotlinx.coroutines.*
fun main() = runBlocking {
    val job = launch {
        delay(1000)
    }
    job.invokeOnCompletion { cause ->
        if (cause != null) println("Ended with: ${cause::class.simpleName}")
        else println("Completed normally")
    }
    delay(50)
    job.cancel()
    job.join()
}

確認問題

finally ブロック内で withContext(NonCancellable) を使う目的は何ですか。

まとめ

キャンセル時にも finally は必ず実行されます。finally 内のサスペンド呼び出しを withContext(NonCancellable) でラップすると、キャンセル後も処理を完了できます。AutoCloseable リソースには use { } を使ってください。

無料で開始

AI チューターと学ぶ Kotlin — 無料

ブラウザでリアルコードを書いて実行し、24/7 の AI チューターから瞬時にサポートを受け、ウェブまたはアプリで続きから学習できます。

コース
51
レッスン
203

よくある質問

「finallyとNonCancellableによるクリーンアップ」レッスンは無料ですか?

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

「finallyとNonCancellableによるクリーンアップ」で何を学びますか?

finallyとwithContext(NonCancellable)を使い、キャンセルされたコルーチンでリソースを解放します。 ブラウザで直接実行するハンズオンコードでKotlin Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

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

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

「finallyとNonCancellableによるクリーンアップ」レッスンにはどのくらい時間がかかりますか?

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

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

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

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

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