Kotlinでコルーチンのキャンセルを安全に処理する方法を徹底解説

Kotlinでの非同期プログラミングを効率的に行う手段としてコルーチンが広く利用されています。しかし、非同期処理が長時間かかる場合や、ユーザーの操作によって処理を中断する必要がある場合、コルーチンのキャンセルが重要になります。キャンセル処理が適切に実装されていないと、無駄なリソース消費やアプリケーションのクラッシュにつながることがあります。

本記事では、Kotlinのコルーチンにおけるキャンセル処理の基本から、安全にキャンセルを行うためのテクニック、具体的なコード例、注意点まで、徹底的に解説します。キャンセルを適切に管理し、効率的な非同期処理を実現する方法を学んでいきましょう。

目次

コルーチンの基本とキャンセルの仕組み

Kotlinのコルーチンは、軽量な非同期処理を可能にする仕組みです。スレッドをブロックすることなく並行処理ができ、効率よくタスクを管理できます。しかし、すべての非同期処理が最後まで完了するとは限らず、状況によっては処理を中断・キャンセルする必要があります。

コルーチンのキャンセルの仕組み

コルーチンのキャンセルは協調的に行われます。これは、コルーチン自身がキャンセル状態を確認し、処理を中断するという意味です。Kotlinのコルーチンでは、キャンセルリクエストが送られると、以下の流れでキャンセルが行われます。

  1. キャンセルシグナルがコルーチンに送られる。
  2. コルーチンが定期的にキャンセル状態を確認し、isActivefalseになった時点でキャンセル処理を行う。
  3. キャンセルされた場合、CancellationExceptionがスローされ、コルーチンが終了する。

簡単なキャンセルのコード例

import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        repeat(1000) { i ->
            if (!isActive) return@launch  // キャンセル状態を確認
            println("Job: I am working on $i ...")
            delay(500L)
        }
    }
    delay(1300L)
    println("Main: I am tired of waiting!")
    job.cancel()  // キャンセルリクエストを送信
    job.join()    // ジョブの終了を待つ
    println("Main: Now I can quit.")
}

キャンセルのポイント

  • 協調的なキャンセル: コルーチンは明示的にキャンセル状態を確認する必要があります。
  • リソース管理: キャンセル処理を適切に行わないと、リソースが解放されない可能性があります。

このように、Kotlinのコルーチンのキャンセルは、シンプルながらも強力な仕組みを持っています。次に、キャンセルが必要となる具体的なシナリオについて見ていきましょう。

キャンセルが必要なシナリオとは

Kotlinのコルーチンを使用する際、すべての非同期処理が最後まで完了するわけではありません。状況に応じて処理を中断することで、リソースを効率的に使い、ユーザー体験を向上させることができます。以下に、キャンセルが必要となる代表的なシナリオを紹介します。

1. ユーザー操作による中断

アプリケーションでユーザーが「キャンセル」ボタンを押した場合、処理を即座に中断する必要があります。例えば、ファイルのダウンロードやアップロード処理中にユーザーが操作を中断した場合です。

val downloadJob = launch {
    downloadFile()
}

// ユーザーがキャンセルボタンを押した時
cancelButton.setOnClickListener {
    downloadJob.cancel()
}

2. ネットワークリクエストのタイムアウト

ネットワークリクエストが長引きすぎた場合、リクエストをキャンセルすることでアプリの応答性を保つことができます。withTimeout関数を使えば、一定時間で自動的にキャンセルできます。

try {
    withTimeout(5000L) { // 5秒以内に完了しなければキャンセル
        fetchDataFromServer()
    }
} catch (e: TimeoutCancellationException) {
    println("Request timed out!")
}

3. リソースの無駄を避けるため

重い計算処理や非同期タスクが不要になった場合、無駄なリソース消費を避けるためにキャンセルすることが重要です。例えば、画面が切り替わった際に、前の画面で行っていた処理が不要になるケースです。

var calculationJob: Job? = null

fun startCalculation() {
    calculationJob = launch {
        performHeavyCalculation()
    }
}

fun onScreenExit() {
    calculationJob?.cancel()  // 画面終了時に処理をキャンセル
}

4. 複数タスクの同時処理時の管理

複数のコルーチンを同時に起動し、そのうちの一つが成功した時点で他の処理をキャンセルするシナリオです。

val job1 = launch { task1() }
val job2 = launch { task2() }

job1.invokeOnCompletion { 
    job2.cancel() // job1が完了したらjob2をキャンセル
}

まとめ

これらのシナリオでは、処理を適切にキャンセルすることで、システムリソースの無駄を防ぎ、効率的なアプリケーション動作を維持できます。次に、キャンセル可能なコルーチンの具体的な作り方を解説します。

キャンセル可能なコルーチンの作り方

Kotlinのコルーチンでキャンセル可能な処理を作るには、キャンセルリクエストを受けたときに安全に中断できるように設計する必要があります。コルーチンは協調的キャンセルをサポートしており、コルーチン自身がキャンセル状態をチェックすることで中断されます。

1. isActiveプロパティを使ったキャンセル確認

isActiveプロパティを使うと、コルーチンがキャンセルされているかどうかを確認できます。isActivefalseの場合、処理を中断します。

import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        repeat(1000) { i ->
            if (!isActive) return@launch  // キャンセルがリクエストされたら中断
            println("Processing $i...")
            delay(500L)
        }
    }
    delay(1300L)
    println("Main: Cancelling the job...")
    job.cancel()  // キャンセルリクエストを送る
    job.join()    // キャンセル後の処理が完了するまで待つ
    println("Main: Job is cancelled.")
}

2. ensureActive関数を使った中断処理

ensureActive関数は、コルーチンがアクティブでない場合にCancellationExceptionをスローして処理を中断します。

import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        repeat(1000) { i ->
            ensureActive()  // キャンセル状態を確認し、アクティブでない場合は例外をスロー
            println("Working on task $i")
            delay(500L)
        }
    }
    delay(1300L)
    println("Main: Requesting cancellation...")
    job.cancel()
    job.join()
    println("Main: Job has been cancelled.")
}

3. yield関数を使ったキャンセルポイント

yield関数を使うと、コルーチンが一時停止し、キャンセルリクエストを確認するポイントを挿入できます。

import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        repeat(1000) { i ->
            yield()  // キャンセル状態を確認し、リクエストがあれば中断
            println("Task $i in progress...")
        }
    }
    delay(1200L)
    println("Main: Cancelling the job...")
    job.cancelAndJoin()
    println("Main: Job is cancelled.")
}

4. 非同期タスク内でのキャンセル確認

長時間の処理を非同期タスクで行う場合も、キャンセル可能にするためには定期的にキャンセル状態を確認する必要があります。

import kotlinx.coroutines.*

suspend fun heavyCalculation() {
    repeat(1000) { i ->
        if (!isActive) return  // キャンセルされている場合は即座に中断
        println("Calculating step $i...")
        delay(300L)
    }
}

fun main() = runBlocking {
    val job = launch { heavyCalculation() }
    delay(1000L)
    println("Main: Requesting cancellation...")
    job.cancel()
    job.join()
    println("Main: Calculation is cancelled.")
}

まとめ

キャンセル可能なコルーチンを作成する際には、以下のポイントを意識しましょう:

  1. isActive で状態確認する
  2. ensureActive で中断処理を強制する
  3. yield を挿入してキャンセルポイントを作る
  4. 処理が長引く場合は定期的にキャンセル状態をチェックする

次は、isActiveensureActiveを活用する具体的な方法について解説します。

isActiveensureActiveの活用方法

Kotlinのコルーチンで安全にキャンセル処理を行うには、協調的キャンセルの仕組みを利用します。特に、isActiveプロパティとensureActive関数は、キャンセル状態を確認し、処理を中断するために便利です。

isActiveプロパティの使い方

isActiveは、コルーチンの現在の状態を示すプロパティです。キャンセルリクエストが送られるとisActivefalseになります。このプロパティをチェックすることで、安全にキャンセル処理を実装できます。

使用例

import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        repeat(1000) { i ->
            if (!isActive) return@launch  // キャンセル状態を確認し、中断
            println("Processing item $i...")
            delay(500L)
        }
    }

    delay(1300L)
    println("Main: Cancelling the job...")
    job.cancel()  // キャンセルリクエストを送信
    job.join()    // ジョブの終了を待つ
    println("Main: Job is cancelled.")
}

解説

  • if (!isActive) return@launch:キャンセル状態を確認し、isActivefalseの場合は処理を中断。
  • job.cancel():コルーチンにキャンセルリクエストを送信。
  • job.join():ジョブが完全に終了するまで待機。

ensureActive関数の使い方

ensureActive関数は、キャンセルがリクエストされた場合にCancellationExceptionをスローします。これにより、処理を中断し、リソースの解放が行われます。

使用例

import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        repeat(1000) { i ->
            ensureActive()  // キャンセル状態を確認し、アクティブでなければ例外をスロー
            println("Working on task $i...")
            delay(500L)
        }
    }

    delay(1300L)
    println("Main: Cancelling the job...")
    job.cancelAndJoin()  // キャンセル後、ジョブの終了を待つ
    println("Main: Job is cancelled.")
}

解説

  • ensureActive():キャンセルがリクエストされるとCancellationExceptionをスローし、処理が中断される。
  • job.cancelAndJoin():キャンセルとジョブの終了待ちを同時に行う。

isActiveensureActiveの違い

特性isActiveensureActive
動作Boolean値として状態を確認キャンセル時に例外をスロー
使い方if (!isActive) returnensureActive()
用途状態確認を柔軟に行いたい場合即座に処理を中断したい場合
処理の中断明示的にreturnで処理を終了例外により処理が強制終了

キャンセル確認を組み込んだ非同期処理

長時間の非同期処理では、定期的にisActiveまたはensureActiveでキャンセル状態を確認しましょう。

suspend fun heavyTask() {
    repeat(1000) { i ->
        ensureActive()  // キャンセルリクエストを確認
        println("Processing step $i...")
        delay(300L)  // 時間のかかる処理
    }
}

まとめ

  • isActive:状態を柔軟に確認し、明示的に中断する。
  • ensureActive:キャンセル時に例外をスローし、即座に中断する。

これらのメカニズムを適切に活用することで、Kotlinのコルーチンを安全かつ効率的にキャンセルできます。次に、非同期処理でキャンセルを安全に行うベストプラクティスを見ていきましょう。

非同期処理でキャンセルを安全に行う

Kotlinのコルーチンにおける非同期処理で安全にキャンセルを行うには、キャンセル状態の確認やリソース管理が重要です。キャンセル処理を正しく実装しないと、リソースリークや予期しない動作が発生することがあります。ここでは、安全にキャンセルを行うためのベストプラクティスを紹介します。

1. キャンセル状態を定期的に確認する

非同期処理が長時間にわたる場合、定期的にキャンセル状態を確認することで安全に中断できます。isActiveensureActiveを使用し、協調的にキャンセル処理を実装しましょう。

コード例

import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        repeat(1000) { i ->
            if (!isActive) return@launch  // キャンセル状態を確認
            println("Processing item $i...")
            delay(300L)
        }
    }
    delay(1000L)
    println("Main: Requesting cancellation...")
    job.cancel()
    job.join()
    println("Main: Job is cancelled.")
}

2. リソースのクリーンアップを行う

キャンセルが発生した場合、リソースを適切に解放する処理を追加しましょう。例えば、ファイルストリームやネットワークリソースを確実に閉じるようにします。

try-finallyブロックを使用した例

import kotlinx.coroutines.*
import java.io.File

fun main() = runBlocking {
    val job = launch {
        val file = File("data.txt").bufferedWriter()
        try {
            repeat(1000) { i ->
                ensureActive()
                file.write("Processing item $i\n")
                delay(500L)
            }
        } finally {
            file.close()  // リソースの解放
            println("File closed.")
        }
    }
    delay(1500L)
    println("Main: Cancelling the job...")
    job.cancelAndJoin()
    println("Main: Job is cancelled.")
}

3. withContextでキャンセル可能なブロックを作成

withContext関数は、指定したディスパッチャーでキャンセル可能なブロックを実行するのに適しています。

使用例

import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        try {
            withContext(Dispatchers.IO) {
                for (i in 1..5) {
                    println("Downloading chunk $i...")
                    delay(500L)
                }
            }
        } catch (e: CancellationException) {
            println("Download cancelled.")
        }
    }

    delay(1000L)
    println("Main: Cancelling download...")
    job.cancelAndJoin()
    println("Main: Download is cancelled.")
}

4. タイムアウトを設定する

withTimeout関数を使うと、一定時間内に処理が完了しない場合に自動的にキャンセルされます。

タイムアウトの例

import kotlinx.coroutines.*

fun main() = runBlocking {
    try {
        withTimeout(1000L) {
            repeat(5) { i ->
                println("Processing $i...")
                delay(500L)
            }
        }
    } catch (e: TimeoutCancellationException) {
        println("Operation timed out.")
    }
    println("Main: Done.")
}

5. キャンセル時の例外処理

キャンセル時に発生するCancellationExceptionは、通常の例外と区別して処理します。キャンセル時のクリーンアップやログ出力を適切に行いましょう。

例外処理の例

import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        try {
            repeat(1000) { i ->
                println("Working on task $i...")
                delay(500L)
            }
        } catch (e: CancellationException) {
            println("Job was cancelled: ${e.message}")
        } finally {
            println("Cleaning up resources...")
        }
    }

    delay(1200L)
    println("Main: Requesting cancellation...")
    job.cancel(CancellationException("User requested cancellation"))
    job.join()
    println("Main: Job is cancelled.")
}

まとめ

非同期処理で安全にキャンセルを行うためのポイント:

  1. キャンセル状態を定期的に確認 (isActiveensureActive)
  2. リソースのクリーンアップを忘れない (try-finallyブロック)
  3. withContextでキャンセル可能なブロックを作る
  4. タイムアウトを設定して自動キャンセルを実装 (withTimeout)
  5. キャンセル時の例外処理を適切に行う

これらのテクニックを組み合わせることで、効率的で安全な非同期処理を実現できます。次に、withTimeoutを使った自動キャンセル処理について詳しく解説します。

withTimeoutを使った自動キャンセル処理

Kotlinのコルーチンで非同期処理が一定時間内に完了しない場合、タイムアウトを設定して自動的にキャンセルする方法があります。withTimeout関数を使用すると、指定した時間が経過すると自動的にキャンセルされ、TimeoutCancellationExceptionがスローされます。

withTimeoutの基本的な使い方

withTimeoutは、指定したミリ秒の間だけ処理を実行し、時間を超えるとキャンセルします。

使用例

import kotlinx.coroutines.*

fun main() = runBlocking {
    try {
        withTimeout(2000L) { // 2秒以内に完了しなければキャンセル
            repeat(5) { i ->
                println("Processing item $i...")
                delay(1000L) // 1秒待機
            }
        }
    } catch (e: TimeoutCancellationException) {
        println("Operation timed out: ${e.message}")
    }
    println("Main: Done.")
}

出力結果

Processing item 0...
Processing item 1...
Operation timed out: Timed out waiting for 2000 ms
Main: Done.

withTimeoutを使う際の注意点

  1. TimeoutCancellationExceptionが発生:
    withTimeoutで設定した時間を超えると、TimeoutCancellationExceptionがスローされます。この例外は自動的にキャンセル処理を行い、呼び出し元に伝えます。
  2. リソースのクリーンアップが必要:
    タイムアウトが発生した場合、ファイルやネットワーク接続などのリソースを適切に解放するため、try-finallyブロックを使うと安全です。

リソースのクリーンアップを伴う例

import kotlinx.coroutines.*
import java.io.File

fun main() = runBlocking {
    val file = File("data.txt").bufferedWriter()
    try {
        withTimeout(1500L) {
            repeat(5) { i ->
                println("Writing data $i...")
                file.write("Data $i\n")
                delay(500L)
            }
        }
    } catch (e: TimeoutCancellationException) {
        println("Writing timed out.")
    } finally {
        file.close()  // ファイルを閉じてリソースを解放
        println("File closed.")
    }
}

出力結果

Writing data 0...
Writing data 1...
Writing data 2...
Writing timed out.
File closed.

withTimeoutOrNullで例外を回避

withTimeoutOrNullを使うと、タイムアウト時にnullが返され、例外が発生しません。処理がキャンセルされるか、正常に完了するかで分岐処理を行いたい場合に便利です。

使用例

import kotlinx.coroutines.*

fun main() = runBlocking {
    val result = withTimeoutOrNull(1500L) {
        repeat(5) { i ->
            println("Processing item $i...")
            delay(500L)
        }
        "Completed" // 正常に完了した場合の結果
    }

    if (result == null) {
        println("Operation timed out.")
    } else {
        println("Result: $result")
    }
}

出力結果

Processing item 0...
Processing item 1...
Processing item 2...
Operation timed out.

withTimeoutの活用例

  1. ネットワークリクエストのタイムアウト
    長いリクエスト処理に対して適切なタイムアウトを設定することで、アプリの応答性を保ちます。
  2. データベース処理の制限
    データベースのクエリが長引く場合、処理時間を制限してパフォーマンスを向上させます。
  3. UI操作の中断
    ユーザーインターフェースでの操作が一定時間内に完了しない場合、自動的に処理をキャンセルして次の操作を可能にします。

まとめ

  • withTimeout:指定時間内に処理が完了しなければTimeoutCancellationExceptionをスローしてキャンセル。
  • withTimeoutOrNull:タイムアウト時にnullを返し、例外を回避。
  • リソース管理:タイムアウト時のリソース解放をtry-finallyブロックで実装。

次は、キャンセル処理で発生する例外の適切な取り扱いについて詳しく解説します。

キャンセル処理での例外の取り扱い

Kotlinのコルーチンでキャンセル処理を行う際、CancellationExceptionが発生することがあります。この例外を正しく理解し、適切に処理することで、リソースのリークや予期しない挙動を防ぐことができます。

キャンセル時に発生するCancellationException

コルーチンがキャンセルされると、通常はCancellationExceptionがスローされます。この例外は、他の例外とは異なり、コルーチンの正常な終了として扱われます。

基本的な例

import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        try {
            repeat(1000) { i ->
                println("Processing $i...")
                delay(500L)
            }
        } catch (e: CancellationException) {
            println("Job was cancelled: ${e.message}")
        } finally {
            println("Cleaning up resources...")
        }
    }

    delay(1300L)
    println("Main: Requesting cancellation...")
    job.cancel(CancellationException("User requested cancellation"))
    job.join()
    println("Main: Job is cancelled.")
}

出力結果

Processing 0...
Processing 1...
Processing 2...
Job was cancelled: User requested cancellation
Cleaning up resources...
Main: Job is cancelled.

CancellationExceptionの性質

  • 通常の終了CancellationExceptionはコルーチンが正常に終了したことを示すため、他の例外と異なり、キャッチして処理する必要はありません。
  • 自動伝播CancellationExceptionは自動的に親コルーチンに伝播し、キャンセルを連鎖的に行います。
  • 例外処理の中で再スローするCancellationExceptionを捕捉した場合でも、必要に応じて再スローすることでキャンセルを継続できます。

CancellationExceptionを再スローする

キャンセル時にリソースのクリーンアップを行う際、CancellationExceptionを再スローすることで、キャンセルの状態を保持できます。

再スローの例

import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        try {
            repeat(1000) { i ->
                println("Working on task $i...")
                delay(500L)
            }
        } catch (e: CancellationException) {
            println("Cleaning up before rethrowing...")
            throw e  // 再スローしてキャンセル処理を継続
        } finally {
            println("Resources released.")
        }
    }

    delay(1300L)
    println("Main: Cancelling the job...")
    job.cancelAndJoin()
    println("Main: Job is cancelled.")
}

出力結果

Working on task 0...
Working on task 1...
Working on task 2...
Cleaning up before rethrowing...
Resources released.
Main: Job is cancelled.

キャンセルと他の例外の取り扱い

キャンセル処理中に別の例外が発生した場合、キャンセルを優先して処理する必要があります。

他の例外との組み合わせ

import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        try {
            repeat(1000) { i ->
                println("Processing $i...")
                if (i == 2) throw Exception("Unexpected error")
                delay(500L)
            }
        } catch (e: Exception) {
            println("Caught exception: ${e.message}")
        } finally {
            println("Cleaning up resources...")
        }
    }

    delay(1500L)
    println("Main: Cancelling the job...")
    job.cancelAndJoin()
    println("Main: Job is cancelled.")
}

出力結果

Processing 0...
Processing 1...
Processing 2...
Caught exception: Unexpected error
Cleaning up resources...
Main: Job is cancelled.

例外処理のベストプラクティス

  1. キャンセル時のクリーンアップ
    finallyブロックを使ってリソースを確実に解放する。
  2. CancellationExceptionの再スロー
    キャンセル処理を継続するために、CancellationExceptionを再スローする。
  3. 他の例外との区別
    キャンセルの例外と他の例外を明確に区別し、適切に処理する。
  4. タイムアウトとキャンセル
    withTimeoutwithTimeoutOrNullを使用する際は、タイムアウト時の処理を適切に記述する。

まとめ

  • CancellationExceptionはコルーチンの正常終了として扱われる。
  • キャンセル時にリソースの解放や後処理をfinallyブロックで実装する。
  • キャンセル処理中に例外が発生した場合は、必要に応じて再スローする。

次は、ネットワークリクエストにおけるキャンセル処理の実践例を見ていきましょう。

実践例:ネットワークリクエストのキャンセル

Kotlinのコルーチンを使ったネットワークリクエストでキャンセル処理を実装するのは、モバイルアプリやWebサービスの開発において非常に重要です。ユーザーが画面を離れたり、リクエストがタイムアウトした場合に、無駄なリソース消費を防ぐためにキャンセルを適切に行う必要があります。

ネットワークリクエストの基本的なキャンセル

KtorなどのHTTPクライアントライブラリを使用したネットワークリクエストのキャンセル例を見てみましょう。

依存関係の追加

GradleファイルにKtorクライアントの依存関係を追加します。

implementation("io.ktor:ktor-client-core:2.0.0")
implementation("io.ktor:ktor-client-cio:2.0.0")

ネットワークリクエストのキャンセル処理

import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import kotlinx.coroutines.*

suspend fun fetchData(url: String) {
    val client = HttpClient(CIO)

    val job = CoroutineScope(Dispatchers.IO).launch {
        try {
            println("Requesting data from $url")
            val response: HttpResponse = client.get(url)
            println("Response received: ${response.status}")
        } catch (e: CancellationException) {
            println("Request was cancelled")
        } catch (e: Exception) {
            println("Request failed: ${e.message}")
        } finally {
            client.close()
            println("Client closed")
        }
    }

    delay(2000L) // 2秒後にキャンセル
    println("Cancelling the request...")
    job.cancelAndJoin()
    println("Main: Request cancelled")
}

fun main() = runBlocking {
    fetchData("https://jsonplaceholder.typicode.com/posts")
}

解説

  1. HttpClient(CIO):
    KtorのCIOエンジンを使用してHTTPクライアントを作成します。
  2. キャンセル可能なジョブ:
    CoroutineScope(Dispatchers.IO).launchで非同期リクエストを開始します。
  3. job.cancelAndJoin():
    2秒後にネットワークリクエストをキャンセルし、ジョブが終了するのを待ちます。
  4. エラーハンドリング:
  • CancellationException:リクエストがキャンセルされた場合の処理。
  • Exception:その他のエラーをキャッチして処理。

出力例

Requesting data from https://jsonplaceholder.typicode.com/posts
Cancelling the request...
Request was cancelled
Client closed
Main: Request cancelled

タイムアウトを使ったネットワークリクエストのキャンセル

withTimeoutを使って、リクエストにタイムアウトを設定する方法もあります。

import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import kotlinx.coroutines.*

suspend fun fetchDataWithTimeout(url: String) {
    val client = HttpClient(CIO)

    try {
        withTimeout(3000L) { // 3秒のタイムアウト
            println("Requesting data from $url")
            val response: HttpResponse = client.get(url)
            println("Response received: ${response.status}")
        }
    } catch (e: TimeoutCancellationException) {
        println("Request timed out")
    } catch (e: Exception) {
        println("Request failed: ${e.message}")
    } finally {
        client.close()
        println("Client closed")
    }
}

fun main() = runBlocking {
    fetchDataWithTimeout("https://jsonplaceholder.typicode.com/posts")
}

出力例(3秒以内にレスポンスが来なかった場合)

Requesting data from https://jsonplaceholder.typicode.com/posts
Request timed out
Client closed

注意点

  1. リソースのクリーンアップ:
    ネットワークリクエストがキャンセルされた場合、必ずHTTPクライアントを閉じてリソースを解放しましょう。
  2. タイムアウト設定:
    ユーザー体験を向上させるため、適切なタイムアウト時間を設定することが重要です。
  3. エラーハンドリング:
    キャンセル以外にも、ネットワークエラーやサーバーエラーに対応する処理を実装しましょう。

まとめ

  • キャンセルリクエスト: job.cancelAndJoin()でネットワークリクエストを中断。
  • タイムアウト処理: withTimeoutで自動的にキャンセルする仕組みを導入。
  • リソース管理: キャンセル後はHTTPクライアントを確実に閉じる。

次は、Kotlinのコルーチンにおけるキャンセル処理のポイントをまとめます。

まとめ

本記事では、Kotlinにおけるコルーチンのキャンセル処理について、基本から具体的な実践方法まで詳しく解説しました。キャンセル処理は非同期プログラムを効率的に管理し、リソースの無駄や予期しない挙動を防ぐために重要です。

以下がキャンセル処理を安全に行うためのポイントです:

  1. 協調的キャンセルisActiveensureActiveを使用してキャンセル状態を定期的に確認する。
  2. リソースのクリーンアップtry-finallyブロックを活用し、キャンセル時にリソースを確実に解放する。
  3. タイムアウト処理withTimeoutwithTimeoutOrNullで自動的にキャンセルする仕組みを導入する。
  4. ネットワークリクエストのキャンセル:Ktorクライアントなどを使った非同期リクエストでキャンセル処理を適切に実装する。
  5. 例外処理CancellationExceptionを正しく扱い、他の例外と区別する。

これらの手法を活用することで、効率的で堅牢な非同期プログラムを構築できます。Kotlinのコルーチンを活かして、キャンセル処理を適切に管理し、快適なアプリケーション開発を目指しましょう。

コメント

コメントする

目次