【问题标题】:CoroutineExceptionHandler not executed when provided as launch context作为启动上下文提供时未执行 CoroutineExceptionHandler
【发布时间】:2019-05-03 17:02:52
【问题描述】:

当我运行这个时:

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 处理程序应该被执行。但事实并非如此。这是为什么呢?

【问题讨论】:

    标签: kotlin coroutine kotlinx.coroutines


    【解决方案1】:

    我相信答案就在official coroutines docs的这一部分:

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

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

    (强调我的)

    此处描述的内容不仅适用于 runBlockingGlobalScope,还适用于任何非顶级协程构建器和自定义范围。

    为了说明(使用 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 执行时的处理程序,但不是提供给runBlocking 内的任何launch 的处理程序。

    TLDR

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

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

    【讨论】:

    • 更奇怪的是:如果根协程是asyncproduce,异常处理程序将不起作用。理由是您将在 await/receive 中遇到异常,但 catch 块并不能阻止整个范围在您到达这些行之前失败。
    【解决方案2】:

    您的kotlinx.coroutines 版本是什么?由于 0.26.0 独立的 launch builder 现在已弃用,您应该改用 GlobalScope.launch

    我尝试了您的示例,更改后它起作用了。

    Kotlinx.coroutines changelog

    【讨论】:

    • 谢谢@Pawel。我正在使用v1.0.0。在代码示例中,我以为我没有使用独立的launch。我以为我使用的是CoroutineScope 上定义的launch,它由runBlocking 自动生成。
    • @JulianA。如果您想使用该范围,您需要将您的launch(eh) 替换为launch(Job() + eh) 以安装异常处理程序。我认为第一种情况甚至编译但不工作只是CoroutineExceptionHandler 实现范围/上下文堆叠所需的内部协程接口的一个怪癖(我仍然不确定它是如何工作的),但它本身没有异常处理程序。跨度>
    • OP 肯定是在使用CoroutoneScope.launch 扩展功能。接收者this 是隐含的。它应该起作用了。我认为您作为父母注入新工作的建议不好,它应该是现有工作的孩子。
    • 语法起作用的原因是一般的而非神奇的:CoroutineExceptionHandlerCoroutineContext.Element,它是CoroutineContext 的子类型。
    猜你喜欢
    • 1970-01-01
    • 2017-09-05
    • 2017-07-28
    • 1970-01-01
    • 1970-01-01
    • 2014-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多