0Pricing
Kotlin Academy · 课时

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 反馈 — 无需本地设置。

此课程中的所有课时

  1. SupervisorJob 与 Job:故障隔离
  2. CoroutineExceptionHandler:全局未捕获异常处理器
  3. async/await 异常传播
  4. 设计弹性的协程架构
← 返回 Kotlin Academy