我正在研究协程中的异常处理机制,以及不同协程构建器在行为上的差异。
在启动时,异常被抛出,并根据代码被捕获(即在try-catch或协程异常处理程序或线程的未捕获异常处理程序中(如果没有什么可捕获的话)
使用async,异常被封装在延迟对象中,直到我们调用'await'才被抛出。
下面的代码:
fun main() {
val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
println("Caught exception $throwable in CoroutineExceptionHandler")
}
val scope = CoroutineScope(Job() + exceptionHandler);
val deferred = scope.async {
functionThatThrows()
}
scope.launch {
deferred.await()
}
Thread.sleep(1000);
}
private fun functionThatThrows() {
throw RuntimeException("");
}
这里的异常应该已经被抛出(因为deferred.await()被调用),并在异常处理程序中被捕获,但它没有,什么也没有被打印出来。有趣的是,* 如果我在async块中添加一个delay语句(在调用functionThatThrows()之前),则会在异常处理程序中抛出并捕获异常。
延迟如何影响这里的异常处理机制?我不明白这一点。
PS:根据下面评论的观察,这在Kotlinplayground中运行良好,但在Android Studio中的行为不同(至少在我的本地机器上)
1条答案
按热度按时间4bbkushb1#
您的示例中存在争用条件。如果在到达
launch()
之前执行了async()
的主体,则异常已经被抛出,整个scope
被认为是不活动的。那么launch()
就没有影响。即使到达
await()
并抛出异常,此时协程机制也会开始执行两个独立的异常处理机制。一个是通过deferred
传递异常,另一个是取消scope
中的所有协程。第一个会从await()
抛出,第二个不会(从技术上讲,它也会抛出,但一个特殊的CancellationException
不会被认为是失败)。我不确定协程是否保证在这种情况下首先发生什么。使用async,异常被封装在延迟对象中,直到我们调用'await'才被抛出。
其实,这并不完全正确。
async {}
中抛出的异常仍然根据结构化并发概念进行传播。我们可以从这个简单的例子中看到这一点:即使我们根本不使用
async()
的结果,它仍然会阻止我们的父协程继续。在使用协程作用域时也是如此:抛出异常后,
scope
不再处于活动状态,它不再执行协程。我假设混淆来自文档中的这个片段:
协程构建器有两种类型:自动传播异常(launch和actor)或向用户公开异常(async和produce)。当这些构建器用于创建一个根协程时,它不是另一个协程的子进程,前一个构建器将异常视为未捕获的异常,类似于Java的Thread.uncaughtExceptionHandler,而后者依赖于用户来消费最终的异常,例如通过await或receive(产生和接收将在Channels部分中介绍)。
https://kotlinlang.org/docs/exception-handling.html#exception-propagation
我不能完全解释它的含义与上面提供的例子有关,这对我来说也有点困惑。我认为这部分文档严格地集中在如何处理传播到协程树顶部的异常,例如向用户显示它们。即使我们从
await()
或通过使用CoroutineExceptionHandler
使用异常,这并不意味着从结构化并发的Angular 考虑处理该异常。它仍然使整个范围失败并使其不可用。