我相信答案就在本节中官方协程文档:
如果协程遇到 CancellationException 以外的异常,它会取消带有该异常的父级。此行为无法重写,用于为不依赖于 CoroutineExceptionHandler 实现的结构化并发提供稳定的协程层次结构。当其所有子级终止时,原始异常由父级处理。
这也是在这些示例中 CoroutineExceptionHandler 始终安装到在 GlobalScope 中创建的协程的原因。将异常处理程序安装到在主 runBlocking 范围内启动的协程是没有意义的,因为尽管安装了处理程序,但当其子协程异常完成时,主协程总是会被取消.
(强调我的)
这里描述的内容不仅适用于runBlocking
and GlobalScope
,但任何非顶级协程构建器和自定义范围。
举例说明(使用 kotlinx.coroutines v1.0.0):
fun f() = runBlocking {
val h1 = CoroutineExceptionHandler { _, e ->
trace("handler 1 e: $e")
}
val h2 = CoroutineExceptionHandler { _, e ->
trace("handler 2 e: $e")
}
val cs = CoroutineScope(newSingleThreadContext("t1"))
trace("launching j1")
val j1 = cs.launch(h1) {
delay(1000)
trace("launching j2")
val j2 = launch(h2) {
delay(500)
trace("throwing exception")
throw RuntimeException("error!")
}
j2.join()
}
trace("joining j1")
j1.join()
trace("exiting f")
}
f()
Output:
[main @coroutine#1]: launching j1
[main @coroutine#1]: joining j1
[t1 @coroutine#2]: launching j2
[t1 @coroutine#3]: throwing exception
[t1 @coroutine#2]: handler 1 e: java.lang.RuntimeException: error!
[main @coroutine#1]: exiting f
请注意处理程序h1
被执行,但是h2
不是。这类似于处理程序GlobalScope#launch
执行,但不提供给任何处理程序launch
inside runBlocking
.
TLDR
提供给某个范围的非根协程的处理程序将被忽略。提供给根协程的处理程序,该根协程由launch
将被执行。根协程创建者async
or produce
将忽略任何处理程序。
这种行为是有道理的。如果子协程的处理程序处理了异常,则该协程的所有祖先(直到根协程)将没有明确的方法来继续执行,因为它们都直接或间接地依赖于子协程的成功。
类似的逻辑适用于async
。如果根协程以async
可以使用已安装的处理程序捕获异常,代码如何通过以下方式等待协程的结果await
知道处理异常后如何继续执行吗?与launch
,在这里,除非协程成功,否则代码无法继续。
因此,它只对launch
根协程具有处理异常的能力。