显式地将协程上下文传递给异步调用会产生不同的异常处理行为,而不是将其安装在封闭范围内
Explicitly passing a coroutine context to an async call produces different exception handling behavior vs. installing it in the enclosing scope
以下代码同时输出 "Handled by exception handler" 和 "Caught exception" 消息:
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" 消息。为什么显式向 async
提供 CoroutineContext(其中包括异常处理程序)会导致异常处理程序不处理异常?
答案埋在文档中,here:
Normally, uncaught exceptions can only result from coroutines created using the launch
builder. A coroutine that was created using async
always catches all its exceptions and represents them in the resulting Deferred
object.
和here:
The parent job is inherited from a CoroutineScope
as well, but it can also be overridden with corresponding coroutineContext
element.
第一种情况:
val res = async<String> { throw RuntimeException() }
Kotlin 通过添加新的 Job
实例为新协程创建上下文,该实例是通过协程范围继承的作业的子实例。因此,当此协程失败时,它会通知其父级,然后将其带到已安装的异常处理程序。
第二种情况:
val res = async<String>(context) { throw RuntimeException() }
context
已经包含一个 Job
元素。这会覆盖上述行为,并且不会为新协程创建新作业。因此它的 Job
元素不指向作为父范围的工作。当它失败时,协程不会按照引用的文档将异常传递给处理程序,也不会将其传递给不存在的父级。
经验教训:永远不要将带有 Job
元素的上下文传递给子 async
构建器。
以下代码同时输出 "Handled by exception handler" 和 "Caught exception" 消息:
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" 消息。为什么显式向 async
提供 CoroutineContext(其中包括异常处理程序)会导致异常处理程序不处理异常?
答案埋在文档中,here:
Normally, uncaught exceptions can only result from coroutines created using the
launch
builder. A coroutine that was created usingasync
always catches all its exceptions and represents them in the resultingDeferred
object.
和here:
The parent job is inherited from a
CoroutineScope
as well, but it can also be overridden with correspondingcoroutineContext
element.
第一种情况:
val res = async<String> { throw RuntimeException() }
Kotlin 通过添加新的 Job
实例为新协程创建上下文,该实例是通过协程范围继承的作业的子实例。因此,当此协程失败时,它会通知其父级,然后将其带到已安装的异常处理程序。
第二种情况:
val res = async<String>(context) { throw RuntimeException() }
context
已经包含一个 Job
元素。这会覆盖上述行为,并且不会为新协程创建新作业。因此它的 Job
元素不指向作为父范围的工作。当它失败时,协程不会按照引用的文档将异常传递给处理程序,也不会将其传递给不存在的父级。
经验教训:永远不要将带有 Job
元素的上下文传递给子 async
构建器。