You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin协程:launch与async异常处理差异及原因咨询

Kotlin协程:async与launch的异常处理差异解析

问题

为何未被coroutineScope包裹的async协程中,异常会向上传递至层级结构?而处理launch协程的异常时,为何无需使用coroutineScope?

示例代码

fun main() {
    runBlocking {
        this.launch {// case 1
            try {
                throw java.lang.IndexOutOfBoundsException()
            } catch (e: Exception) {
                println("捕获到IndexOutOfBoundsException")
            }
        }
        try {
            coroutineScope {
                val deferred = async {// case 2
                    throw ArithmeticException()
                }
                deferred.await()

            }
        } catch (e: ArithmeticException) {
            println("捕获到ArithmeticException")
        }
        try {
            val deferred = async {//case 3
                throw ArithmeticException()
            }
            deferred.await()
        } catch (e: Exception) {
            println("捕获到ArithmeticException,但同时传递至根协程")
        }
    }
}

运行输出

捕获到IndexOutOfBoundsException
捕获到ArithmeticException
捕获到ArithmeticException,但同时传递至根协程
Exception in thread "main" java.lang.ArithmeticException
    at TryItYourself3Kt$main$1$deferred$1.invokeSuspend(TryItYourself3.kt:26)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:104)
    at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:277)
    at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:95)
    at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:69)
    at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
    at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:48)
    at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
    at TryItYourself3Kt.main(TryItYourself3.kt:5)
    at TryItYourself3Kt.main(TryItYourself3.kt)

进程退出,代码为1

核心原因解析

1. launch协程的异常处理逻辑

  • launch属于**"即发即忘"**类型的协程,它不返回结果,异常处理逻辑更直接:
    • 可以在launch内部通过try-catch自行捕获异常(如Case1),异常不会自动向上传播到父协程。
    • 若内部未处理,异常会被协程上下文的默认异常处理器捕获(比如runBlocking的根协程会直接抛出异常导致崩溃,但默认情况下你可以在内部处理)。
    • 不需要coroutineScope是因为launch的异常不会主动冒泡到父协程,内部处理即可完成异常管控。

2. async协程的异常处理逻辑

async设计用于返回计算结果(通过Deferred对象),它的异常传播规则和launch完全不同:

  • 当async被coroutineScope包裹时(Case2):
    • coroutineScope是结构化并发的核心作用域,它会等待所有子协程完成,并且会将子协程的异常统一向上传递。
    • 因为coroutineScope是挂起函数,外层可以通过try-catch捕获整个作用域内的异常,同时coroutineScope会取消其他子协程,避免异常扩散到根协程。
  • 当async未被coroutineScope包裹时(Case3):
    • async的父协程是runBlocking的根协程,此时async抛出异常的瞬间就会直接传递给父协程,而不是等到调用await()的时候。
    • 即使你在await()处用try-catch捕获了异常,根协程已经收到了这个异常,最终还是会崩溃。这就是为什么Case3里明明捕获了异常,程序还是会抛出根异常。

总结

  • launch的异常不会自动向上传播,内部处理即可,无需coroutineScope。
  • async的异常在非结构化作用域下会立即传递给父协程,必须通过coroutineScope来将异常限制在局部作用域内,才能被外层try-catch安全捕获,避免扩散到根协程导致程序崩溃。

内容的提问来源于stack exchange,提问作者Shyam Patel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 10:03:12