对异步异常处理感到困惑(Kotlin协程)

7eumitmz  于 2023-06-30  发布在  Kotlin
关注(0)|答案(1)|浏览(147)

我正在研究协程中的异常处理机制,以及不同协程构建器在行为上的差异。
在启动时,异常被抛出,并根据代码被捕获(即在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中的行为不同(至少在我的本地机器上)

4bbkushb

4bbkushb1#

您的示例中存在争用条件。如果在到达launch()之前执行了async()的主体,则异常已经被抛出,整个scope被认为是不活动的。那么launch()就没有影响。
即使到达await()并抛出异常,此时协程机制也会开始执行两个独立的异常处理机制。一个是通过deferred传递异常,另一个是取消scope中的所有协程。第一个会从await()抛出,第二个不会(从技术上讲,它也会抛出,但一个特殊的CancellationException不会被认为是失败)。我不确定协程是否保证在这种情况下首先发生什么。
使用async,异常被封装在延迟对象中,直到我们调用'await'才被抛出。
其实,这并不完全正确。async {}中抛出的异常仍然根据结构化并发概念进行传播。我们可以从这个简单的例子中看到这一点:

suspend fun main() = coroutineScope {
    async {
        error("")
    }

    delay(1000)
    println("Unreachable")
}

即使我们根本不使用async()的结果,它仍然会阻止我们的父协程继续。在使用协程作用域时也是如此:

fun main() {
    val scope = CoroutineScope(EmptyCoroutineContext)

    println(scope.isActive) // true

    scope.async {
        delay(100)
        error("")
    }

    println(scope.isActive) // true
    Thread.sleep(500)
    println(scope.isActive) // false
}

抛出异常后,scope不再处于活动状态,它不再执行协程。
我假设混淆来自文档中的这个片段:
协程构建器有两种类型:自动传播异常(launch和actor)或向用户公开异常(async和produce)。当这些构建器用于创建一个根协程时,它不是另一个协程的子进程,前一个构建器将异常视为未捕获的异常,类似于Java的Thread.uncaughtExceptionHandler,而后者依赖于用户来消费最终的异常,例如通过await或receive(产生和接收将在Channels部分中介绍)。
https://kotlinlang.org/docs/exception-handling.html#exception-propagation
我不能完全解释它的含义与上面提供的例子有关,这对我来说也有点困惑。我认为这部分文档严格地集中在如何处理传播到协程树顶部的异常,例如向用户显示它们。即使我们从await()或通过使用CoroutineExceptionHandler使用异常,这并不意味着从结构化并发的Angular 考虑处理该异常。它仍然使整个范围失败并使其不可用。

相关问题