Kotlin Academy · レッスン

async/awaitによる例外の伝播

asyncから例外がどのように伝播するか、またtry-awaitを使う場面を理解します。

レッスン 3/413 ステップ

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

async は例外を保存する

launch とは異なり、async は例外を返された Deferred に保存します。例外が再スローされるのは、await() を呼び出したときだけです。

import kotlinx.coroutines.*
fun main() = runBlocking {
    val deferred = async {
        throw RuntimeException("async error")
    }
    try {
        deferred.await()  // exception rethrown here
    } catch (e: RuntimeException) {
        println("Caught: ${e.message}")
    }
}

await() しない場合の例外

await() を一度も呼び出さないと、通常の Job を親とする場合、例外は暗黙的に破棄されます。supervisorScope と必ず await するパターンを推奨します。

import kotlinx.coroutines.*
fun main() = runBlocking {
    // Exception stored in deferred, never retrieved:
    val d = async { throw RuntimeException("lost exception") }
    delay(100)  // d has failed — exception never surfaced
    println("d.isCancelled: ${d.isCancelled}")
}

coroutineScope 内の async

通常の coroutineScope では、async の子が例外をスローし、その例外が捕捉されずに伝播すると、スコープとすべての兄弟がキャンセルされます。

import kotlinx.coroutines.*
fun main() = runBlocking {
    try {
        coroutineScope {
            val a = async { "A" }
            val b = async { throw RuntimeException("B failed") }
            println(a.await())
            println(b.await())  // propagates, cancels scope
        }
    } catch (e: RuntimeException) {
        println("Scope failed: ${e.message}")
    }
}

supervisorScope 内の async

supervisorScope では、async からの例外が兄弟に伝播することはありません。各 await() は個別にラップする必要があります。

import kotlinx.coroutines.*
fun main() = runBlocking {
    supervisorScope {
        val a = async { "A" }
        val b = async { throw RuntimeException("B failed") }
        println(a.await())
        try { println(b.await()) }
        catch (e: RuntimeException) { println("b failed: ${e.message}") }
    }
}

複数の async には awaitAll

awaitAll(d1, d2, d3) はすべての Deferred を待機します。いずれかが失敗すると、すぐに例外をスローして他の Deferred をキャンセルします。

import kotlinx.coroutines.*
fun main() = runBlocking {
    try {
        val results = awaitAll(
            async { "result1" },
            async { throw RuntimeException("task 2 failed") },
            async { "result3" }
        )
        println(results)
    } catch (e: Exception) {
        println("awaitAll failed: ${e.message}")
    }
}

async と runCatching

各 await() を runCatching でラップすると、処理を途中で打ち切らずに結果とエラーを収集できます。

import kotlinx.coroutines.*
fun main() = runBlocking {
    supervisorScope {
        val deferreds = listOf(
            async { "A" },
            async { throw RuntimeException("B") },
            async { "C" }
        )
        val results = deferreds.map { runCatching { it.await() } }
        results.forEach { println(it) }
    }
}

エラーを蓄積する並列タスク

並列 async タスクで発生したすべての失敗を収集し、すぐに失敗させるのではなく、まとめて報告します。

import kotlinx.coroutines.*
fun main() = runBlocking {
    val tasks = listOf("a", "b", "c")
    val results = supervisorScope {
        tasks.map { t ->
            async {
                if (t == "b") throw RuntimeException("b failed")
                t.uppercase()
            }
        }.map { runCatching { it.await() } }
    }
    val errors = results.filter { it.isFailure }
    val successes = results.mapNotNull { it.getOrNull() }
    println("Success: $successes, Errors: ${errors.size}")
}

例外の型を保持する

async 内でスローされた例外は、その型が保持されます。await() で具体的な例外型を捕捉すると、より正確にエラーを処理できます。

import kotlinx.coroutines.*
class NetworkException(msg: String) : RuntimeException(msg)
fun main() = runBlocking {
    val d = async { throw NetworkException("timeout") }
    try {
        d.await()
    } catch (e: NetworkException) {
        println("Network error: ${e.message}")
    } catch (e: Exception) {
        println("Other error: ${e.message}")
    }
}

非サスペンドチェック用の Deferred.getCompleted()

join() の後に getCompleted() を使用すると、結果を同期的に取得できます。Deferred が失敗している場合は例外をスローします。

import kotlinx.coroutines.*
fun main() = runBlocking {
    val d = async { 42 }
    d.join()  // wait without caring about result
    try {
        val result = d.getCompleted()  // synchronous — no suspend
        println("Result: $result")
    } catch (e: Exception) {
        println("Failed: ${e.message}")
    }
}

構造化並行性と async

構造化並行性では、async の Deferred は常に親スコープにリンクされます。スコープがキャンセルされると Deferred もキャンセルされるため、孤立したタスクは発生しません。

import kotlinx.coroutines.*
fun main() = runBlocking {
    val scope = CoroutineScope(SupervisorJob())
    val d = scope.async {
        delay(1000)
        "result"
    }
    scope.cancel()  // cancels d too
    try { d.await() }
    catch (e: CancellationException) { println("Cancelled as expected") }
}

ベストプラクティス:必ず Await する

結果が必要ない場合でも、すべての async Deferred に対して必ず await() を呼び出します。これにより例外が表面化し、失敗が暗黙的に無視されるのを防げます。

import kotlinx.coroutines.*
fun main() = runBlocking {
    supervisorScope {
        val d = async {
            // do background work
            "done"
        }
        // Always await:
        val result = runCatching { d.await() }
        println(result)
    }
}

クイックチェック

async { throw ... } ブロックの例外は、いつ表面化しますか。

振り返り

async は例外を Deferred に保存し、await() で表面化させます。coroutineScope では、例外の伝播によって兄弟がキャンセルされます。supervisorScope では、各 await でそれぞれの失敗を処理する必要があります。すべての Deferred を必ず await してください。

無料で開始

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

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

コース
51
レッスン
203

よくある質問

「async/awaitによる例外の伝播」レッスンは無料ですか?

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

「async/awaitによる例外の伝播」で何を学びますか?

asyncから例外がどのように伝播するか、またtry-awaitを使う場面を理解します。 ブラウザで直接実行するハンズオンコードでKotlin Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

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

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

「async/awaitによる例外の伝播」レッスンにはどのくらい時間がかかりますか?

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

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

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

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

  1. SupervisorJobとJob:失敗の分離
  2. CoroutineExceptionHandler:グローバルな未捕捉例外ハンドラー
  3. async/awaitによる例外の伝播
  4. 堅牢なコルーチンアーキテクチャの設計
← Kotlin Academyに戻る