简体   繁体   English

将协程上下文显式传递给异步调用会产生不同的异常处理行为与将其安装在封闭的 scope 中

[英]Explicitly passing a coroutine context to an async call produces different exception handling behavior vs. installing it in the enclosing scope

The following code outputs both the "Handled by exception handler" and the "Caught exception" messages:以下代码输出“由异常处理程序处理”和“捕获异常”消息:

import kotlin.coroutines.*
import kotlinx.coroutines.*

fun main() {
    val eh = CoroutineExceptionHandler { _, e -> println("Handled by exception handler") }
    val context = eh + Job()

    CoroutineScope(context).launch {
        val res = async<String> { throw RuntimeException() }
//        val res = async<String>(context) { throw RuntimeException() }

        try {
            println("Result: ${res.await()}")
        }
        catch (e: Throwable){
            println("Caught exception")
        }
    }


    Thread.sleep(1000)
}

But if I swap which "val res" line is commented in, I only get the "Caught exception" message.但是,如果我交换注释了哪个“val res”行,我只会收到“Caught exception”消息。 Why does explictly providing the CoroutineContext (which includes the exception handler) to async result in the exception handler not handling the exception?为什么明确提供 CoroutineContext (包括异常处理程序) async导致异常处理程序不处理异常?

The answer is buried in the documentation, here :答案隐藏在文档中, 这里

Normally, uncaught exceptions can only result from coroutines created using the launch builder.通常,未捕获的异常只能由使用launch构建器创建的协程产生。 A coroutine that was created using async always catches all its exceptions and represents them in the resulting Deferred object.使用async创建的协程始终捕获其所有异常并在生成的Deferred object 中表示它们。

and here : 在这里

The parent job is inherited from a CoroutineScope as well, but it can also be overridden with corresponding coroutineContext element.父作业也继承自CoroutineScope ,但也可以用相应的coroutineContext元素覆盖。

In the first case:在第一种情况下:

val res = async<String> { throw RuntimeException() }

Kotlin creates the context for the new coroutine by adding a new Job instance that is the child of the job inherited through the coroutine scope. Kotlin 通过添加一个新的Job实例来为新的协程创建上下文,该实例是通过协程 scope 继承的作业的子级。 Therefore when this coroutine fails, it notifies its parent, which then takes it to the installed exception handler.因此,当此协程失败时,它会通知其父级,然后将其带到已安装的异常处理程序。

In the second case:在第二种情况下:

val res = async<String>(context) { throw RuntimeException() }

context already contains a Job element. context已经包含一个Job元素。 This overrides the behavior above and no new job is created for the new coroutine.这会覆盖上述行为,并且不会为新协程创建新作业。 Therefore its Job element does not point to the scope's job as the parent.因此,它的Job元素并不指向作为父范围的作业。 When it fails, the coroutine does not pass the exception to the handler as per the quoted documentation, and it also does not pass it to the nonexistent parent.当它失败时,协程不会根据引用的文档将异常传递给处理程序,也不会将其传递给不存在的父级。

Lesson learned: never pass a context with a Job element to a child async builder.经验教训:永远不要将带有Job元素的上下文传递给子async构建器。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM