首页 > 解决方案 > 作为启动上下文提供时未执行 CoroutineExceptionHandler

问题描述

当我运行这个:

fun f() = runBlocking {
    val eh = CoroutineExceptionHandler { _, e -> trace("exception handler: $e") }
    val j1 = launch(eh) {
        trace("launched")
        delay(1000)
        throw RuntimeException("error!")
    }
    trace("joining")
    j1.join()
    trace("after join")
}
f()

这是输出:

[main @coroutine#1]: joining
[main @coroutine#2]: launched
java.lang.RuntimeException: error!
    at ExceptionHandling$f9$1$j1$1.invokeSuspend(ExceptionHandling.kts:164)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)
    at kotlinx.coroutines.ResumeModeKt.resumeMode(ResumeMode.kt:67)

根据CoroutineExceptionHandler的文档,我提供的eh处理程序应该被执行。但事实并非如此。这是为什么?

标签: kotlincoroutinekotlinx.coroutines

解决方案


我相信答案就在官方协程文档的这一部分:

如果协程遇到除 CancellationException 以外的异常,它会取消其父级并出现该异常。此行为不能被覆盖,并用于为不依赖于 CoroutineExceptionHandler 实现的结构化并发提供稳定的协程层次结构。当所有子级终止时,原始异常由父级处理。

这也是为什么在这些示例中,总是将 CoroutineExceptionHandler 安装到在 GlobalScope 中创建的协程中的原因。将异常处理程序安装到在主 runBlocking 范围内启动的协程是没有意义的,因为尽管安装了处理程序,但当其子协程以异常完成时,主协程总是会被取消

(强调我的)

此处描述的内容不仅适用于runBlockingand 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()

输出:

[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内部的处理程序runBlocking

TLDR

提供给作用域的非根协程的处理程序将被忽略。将执行提供给根协程的处理程序。

正如 Marko Topolnik 在下面的评论中正确指出的那样,上述概括仅适用于由launch. 那些由创建asyncproduce将始终忽略所有处理程序。


推荐阅读