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
相关产品推荐
相关产品推荐

