async/await 异常传播
理解 async 抛出的异常如何传播,以及何时使用 try-await。
async/await 异常传播 是 CoddyKit 上的免费 Kotlin Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Kotlin Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Kotlin Academy 课程共包含 4 节课。
异步操作会存储异常
与 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 下的异步操作
在普通 coroutineScope 下,如果异步子协程抛出异常且异常继续传播(未被捕获),它会取消作用域及所有兄弟协程。
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 下的异步操作
在 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}") }
}
}使用 awaitAll 等待多个异步操作
awaitAll(d1, d2, d3) 会等待所有延迟对象。如果任何一个失败,它会立即抛出异常并取消其他对象。
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}")
}
}将 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) }
}
}累积错误的并行任务
收集并行异步任务中的所有失败,并将它们一起报告,而不是遇到第一个错误就立即失败。
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() 同步获取结果。如果延迟对象失败,它会抛出异常。
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 延迟对象始终与其父作用域关联。作用域被取消时,延迟对象也会被取消——不会产生孤立任务。
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") }
}最佳实践:始终等待
始终对每个 async 延迟对象调用 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 都必须处理自身的失败。始终等待每个延迟对象。
常见问题解答
「async/await 异常传播」课时是免费的吗?
是的 — 「async/await 异常传播」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Kotlin Academy 课程的其余内容,请升级到 CoddyKit PRO。 Kotlin Academy 课程共包含 4 节课。
「async/await 异常传播」这节课中我会学到什么?
理解 async 抛出的异常如何传播,以及何时使用 try-await。 你通过在浏览器中直接运行的动手代码来练习 Kotlin Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Kotlin Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Kotlin Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。
「async/await 异常传播」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Kotlin Academy 课中编写并运行代码吗?
能。每节 Kotlin Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。