[英]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 usingasync
always catches all its exceptions and represents them in the resultingDeferred
object.使用
async
创建的协程始终捕获其所有异常并在生成的Deferred
object 中表示它们。
The parent job is inherited from a
CoroutineScope
as well, but it can also be overridden with correspondingcoroutineContext
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.