Kotlin Academy · レッスン

withTimeoutとwithTimeoutOrNull

タイムアウト用ラッパーで実行時間を制限し、TimeoutCancellationExceptionを処理します。

レッスン 2/413 ステップ

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

なぜタイムアウトが必要か

ネットワーク処理や I/O では、コルーチンが無期限に停止することがあります。withTimeout は、指定したミリ秒以内に完了しなかった場合にブロックをキャンセルします。

import kotlinx.coroutines.*
fun main() = runBlocking {
    try {
        withTimeout(200) {
            delay(1000)  // simulates slow network
            println("This never prints")
        }
    } catch (e: TimeoutCancellationException) {
        println("Timed out!")
    }
}

withTimeout の基本

withTimeout(millis) { ... } は、ブロックが制限時間を超えると CancellationException のサブクラスである TimeoutCancellationException をスローします。

import kotlinx.coroutines.*
suspend fun fetchData(): String {
    delay(100)
    return "data"
}
fun main() = runBlocking {
    val result = withTimeout(500) {
        fetchData()
    }
    println(result) // data
}

withTimeoutOrNull

withTimeoutOrNull はタイムアウト時に例外をスローする代わりに null を返すため、タイムアウトを通常の処理フローとして扱いやすくなります。

import kotlinx.coroutines.*
fun main() = runBlocking {
    val result: String? = withTimeoutOrNull(200) {
        delay(1000)
        "done"
    }
    println(result ?: "Timeout — using default")
}

戻り値付きタイムアウト

どちらの関数も、成功した場合はブロック内の最後の式の値を返します。

import kotlinx.coroutines.*
fun main() = runBlocking {
    val response = withTimeoutOrNull(500) {
        delay(100)
        mapOf("status" to 200, "body" to "OK")
    }
    println(response?.get("status")) // 200
}

タイムアウトのネスト

内側のタイムアウトが先に期限切れになります。外側のタイムアウトは、内側のタイムアウトが先にキャンセルしなかった場合にのみ発生します。リクエストごとのタイムアウトと全体のタイムアウトを使い分ける際に便利です。

import kotlinx.coroutines.*
fun main() = runBlocking {
    withTimeoutOrNull(1000) {       // global
        withTimeoutOrNull(200) {    // per-call
            delay(300)
            println("inner done")  // won't print
        } ?: println("Inner timed out")
        delay(100)
        println("outer still running")
    }
}

TimeoutCancellationException

TimeoutCancellationException は CancellationException なので、コルーチンの仕組みでは通常のキャンセルとして扱われ、親スコープには伝播しません。

import kotlinx.coroutines.*
fun main() = runBlocking {
    val job = launch {
        try {
            withTimeout(100) { delay(1000) }
        } catch (e: TimeoutCancellationException) {
            println("Caught in child: ${e.message}")
        }
    }
    job.join()
    println("Parent still running: ${isActive}")
}

タイムアウト時のリソースクリーンアップ

時間切れになった場合でもリソースを解放できるよう、withTimeout 内で finally を使用してください。

import kotlinx.coroutines.*
fun main() = runBlocking {
    val result = withTimeoutOrNull(150) {
        try {
            println("Opening resource")
            delay(300)
            "result"
        } finally {
            println("Closing resource") // always runs
        }
    }
    println("Result: $result")
}

タイムアウト付きリトライ

タイムアウトとリトライ処理を組み合わせます。操作を試行ごとのタイムアウト付きで実行し、結果が null の場合に再試行します。

import kotlinx.coroutines.*
suspend fun tryFetch(attempt: Int): String? = withTimeoutOrNull(200) {
    delay(if (attempt < 3) 300L else 100L) // fails first 2 attempts
    "success on attempt $attempt"
}
fun main() = runBlocking {
    var result: String? = null
    var attempt = 1
    while (result == null) {
        result = tryFetch(attempt++)
    }
    println(result)
}

ViewModel での withTimeout

Android の ViewModel では、viewModelScope.launch 内でリポジトリ呼び出しを withTimeoutOrNull でラップし、応答が遅い場合にエラー状態を表示します。

import kotlinx.coroutines.*
// Pseudocode pattern:
suspend fun loadUser(): String = withTimeoutOrNull(3000) {
    // repo.getUser()
    delay(100)
    "Alice"
} ?: "Timeout — using cached data"
fun main() = runBlocking { println(loadUser()) }

精度に関する注意

withTimeout はコルーチンのディスパッチャーに依存します。TestCoroutineScheduler を使うテストでは時間が仮想化され、手動で進めることができます。

import kotlinx.coroutines.*
// In unit tests with runTest:
// runTest {
//     withTimeout(1000) {
//         delay(999)  // virtual time — completes instantly
//         println("done")
//     }
// }
fun main() = runBlocking {
    println("Use runTest for virtual-time timeout testing")
}

2つの使い分け

タイムアウトをエラーとして扱う場合は withTimeout を使います。タイムアウトが想定される結果である場合(キャッシュミスや任意のプリフェッチなど)は withTimeoutOrNull を使います。

import kotlinx.coroutines.*
fun main() = runBlocking {
    // Mandatory: throw on timeout
    // withTimeout(500) { criticalOp() }

    // Optional: null on timeout
    val cached = withTimeoutOrNull(50) {
        delay(200); "fresh"
    } ?: "stale"
    println(cached)
}

確認問題

ブロックが制限時間を超えたとき、withTimeoutOrNull は何を返しますか。

まとめ

withTimeout はタイムアウト時に例外をスローし、withTimeoutOrNull は null を返します。どちらもブロックを協調的にキャンセルし、finally ブロックを実行します。リソースのクリーンアップには finally を使い、タイムアウトが想定される結果の場合は withTimeoutOrNull を使ってください。

無料で開始

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

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

コース
51
レッスン
203

よくある質問

「withTimeoutとwithTimeoutOrNull」レッスンは無料ですか?

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

「withTimeoutとwithTimeoutOrNull」で何を学びますか?

タイムアウト用ラッパーで実行時間を制限し、TimeoutCancellationExceptionを処理します。 ブラウザで直接実行するハンズオンコードでKotlin Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

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

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

「withTimeoutとwithTimeoutOrNull」レッスンにはどのくらい時間がかかりますか?

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

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

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

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

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