async/awaitによる例外の伝播
asyncから例外がどのように伝播するか、またtry-awaitを使う場面を理解します。
「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フィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- SupervisorJobとJob:失敗の分離
- CoroutineExceptionHandler:グローバルな未捕捉例外ハンドラー
- async/awaitによる例外の伝播
- 堅牢なコルーチンアーキテクチャの設計