kotlin - 将协程上下文显式传递给异步调用会产生不同的异常处理行为与将其安装在封闭范围内
问题描述
以下代码输出“由异常处理程序处理”和“捕获异常”消息:
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)
}
但是,如果我交换注释了哪个“val res”行,我只会收到“Caught exception”消息。为什么显式提供 CoroutineContext(包括异常处理程序)async
导致异常处理程序不处理异常?
解决方案
答案隐藏在文档中,这里:
通常,未捕获的异常只能由使用
launch
构建器创建的协程产生。使用创建的协程async
总是捕获其所有异常并在结果Deferred
对象中表示它们。
在这里:
父作业也继承自 a
CoroutineScope
,但也可以用相应的coroutineContext
元素覆盖。
在第一种情况下:
val res = async<String> { throw RuntimeException() }
Kotlin 通过添加一个新Job
实例来为新协程创建上下文,该实例是通过协程范围继承的作业的子代。因此,当此协程失败时,它会通知其父级,然后将其带到已安装的异常处理程序。
在第二种情况下:
val res = async<String>(context) { throw RuntimeException() }
context
已经包含一个Job
元素。这会覆盖上述行为,并且不会为新协程创建新作业。因此,它的Job
元素并不指向作用域作为父级的工作。当它失败时,协程不会根据引用的文档将异常传递给处理程序,也不会将其传递给不存在的父级。
经验教训:永远不要将带有Job
元素的上下文传递给子async
构建器。