当前位置: 首页 > 知识库问答 >
问题:

延迟引发的异常。runBlocking中的wait()即使在被捕获后也被视为未处理

羊舌兴德
2023-03-14

代码

fun main() {
    runBlocking {
        try {
            val deferred = async { throw Exception() }
            deferred.await()
        } catch (e: Exception) {
            println("Caught $e")
        }
    }
    println("Completed")
}

结果如下:

Caught java.lang.Exception
Exception in thread "main" java.lang.Exception
    at org.mtopol.TestKt$main$1$deferred$1.invokeSuspend(test.kt:11)
    ...

这种行为对我来说没有意义。异常已被捕获并处理,但它仍作为未处理的异常逃逸到顶层。

这种行为是否有文件记录和预期?这违背了我对异常处理应该如何工作的所有直觉。

这个问题我改编自静态编程语言论坛上的一个帖子。

Kotlin文档建议,如果我们不想在一个协同程序失败时取消所有协同程序,就使用supervisorScope。这样我就可以写作了

fun main() {
    runBlocking {
        supervisorScope {
            try {
                launch {
                    delay(1000)
                    println("Done after delay")
                }
                val job = launch {
                    throw Exception()
                }
                job.join()
            } catch (e: Exception) {
                println("Caught $e")
            }
        }
    }
    println("Completed")
}

输出现在是

Exception in thread "main" java.lang.Exception
    at org.mtopol.TestKt$main$2$1$job$1.invokeSuspend(test.kt:16)
    ...
    at org.mtopol.TestKt.main(test.kt:8)
    ...

Done after delay
Completed

这又不是我想要的行为。这里,一个启动ed协程失败,出现了一个未处理的异常,使其他协程的工作无效,但它们不间断地进行。

我认为合理的行为是当协程以不可预见的(即未处理的)方式失败时分散取消。从wait捕获异常意味着没有任何全局错误,只是作为业务逻辑的一部分处理的本地化异常。

共有3个答案

申高卓
2023-03-14

这可以通过稍微改变代码来解决,使延迟值使用与运行阻塞作用域相同的CoroutineContext显式执行。

runBlocking {
    try {
        val deferred = withContext(this.coroutineContext) {
            async {
                throw Exception()
            }
        }
        deferred.await()
    } catch (e: Exception) {
        println("Caught $e")
    }
}
println("Completed")

更新原始问题后更新

这是否提供您想要的:

runBlocking {
    supervisorScope {
        try {
            val a = async {
                delay(1000)
                println("Done after delay")
            }
            val b = async { throw Exception() }
            awaitAll(a, b)
        } catch (e: Exception) {
            println("Caught $e")
            // Optional next line, depending on whether you want the async with the delay in it to be cancelled.
            coroutineContext.cancelChildren()
        }
    }
}

这是从这篇讨论并行分解的评论中摘取的。

益光亮
2023-03-14

虽然所有的答案都是正确的,但让我来解释一下,这可能会帮助其他用户。此处(官方文件)记录如下:-

如果协同程序遇到除CancellationException之外的异常,它会用该异常取消其父进程。此行为不能被重写,并用于为结构化并发提供稳定的协同路由层次结构,该层次结构不依赖于协同路由ExceptionHandler实现。当父级(在GlobalScope中)的所有子级终止时,原始异常由父级(在GlobalScope中)处理。

将异常处理程序安装到在主runBlocking范围内启动的协程中是没有意义的,因为尽管安装了异常处理程序,但当其子进程异常完成时,主协程总是会被取消。

希望这能有所帮助。

班言
2023-03-14

在研究了Kotlin引入这种行为的原因后,我发现,如果异常不是以这种方式传播的,那么编写及时取消的行为良好的代码会很复杂。例如:

runBlocking {
    val deferredA = async {
        Thread.sleep(10_000)
        println("Done after delay")
        1
    }
    val deferredB = async<Int> { throw Exception() }
    println(deferredA.await() + deferredB.await())
}

因为a是我们碰巧等待的第一个结果,所以此代码将持续运行10秒,然后导致错误,并且没有完成任何有用的工作。在大多数情况下,我们希望在一个组件出现故障时立即取消所有操作。我们可以这样做:

val (a, b) = awaitAll(deferredA, deferredB)
println(a + b)

这段代码不那么优雅:我们被迫在同一个位置等待所有结果,我们失去了类型安全性,因为awaitAll返回所有参数的公共超类型列表。如果我们有

suspend fun suspendFun(): Int {
    delay(10_000)
    return 2
}

我们想要写作

val c = suspendFun()
val (a, b) = awaitAll(deferredA, deferredB)
println(a + b + c)

suspendFun完成之前,我们被剥夺了摆脱困境的机会。我们可以这样做:

val deferredC = async { suspendFun() }
val (a, b, c) = awaitAll(deferredA, deferredB, deferredC)
println(a + b + c)

但这是脆弱的,因为你必须小心,以确保你这样做的每一个暂停调用。这也违背了静态编程语言“默认顺序”的原则

总而言之:目前的设计虽然一开始有悖常理,但作为一个实用的解决方案,它确实有意义。它还加强了不使用async await的规则,除非您正在对任务进行并行分解。

 类似资料:
  • 我的RMI服务器接口声明了一个方法foo(),该方法被声明为引发RemoteException和Exception,如下所示: 服务器实现为: 我的客户端在服务器上调用foo: 现在,当我运行客户端时,我得到: 从java类型的foo()中获取异常。rmi。异常异常:未声明的检查异常;嵌套的例外是:java。伊奥。InterruptedIOException:操作超时 Java文档是这样说的。rm

  • 下面是我在Spring boot中的ExceptionHandler类

  • 我正在尝试创建一个过滤器来处理异常(请参见在JSF中处理未捕获的异常) 我在日志中看到错误: 我做错了什么?

  • 我正在实现自定义'AuthenticationProvider'。如果没有经过身份验证,我将在'authenticate'函数中抛出异常,如下所示。 我有全局异常处理程序,如下所示。 当在'authenticate'函数内部引发异常时,不会调用全局异常处理程序。对于所有其他例外情况,它正在被调用。我想在全局异常处理程序中捕获异常并返回自定义错误消息。我怎么能那样做?感谢任何帮助。提前道谢。

  • 下面是我的代码片段: 现在java用'unhandled exception type SQLException)标记指示语句(以及后面的所有语句)。gfsql.dosql抛出此异常并定义为: public ResultSet doSQL(String sqlCommand)抛出SQLException{ 有趣的是--如果我像这样重复“catch”块: 未标记“未处理”错误。(但是,重复的catc

  • 问题内容: 关于程序流,这一直困扰着我一段时间。 我想知道是否有可能从Method中捕获错误,以阻止它执行通常无法跟着它执行的Method,就像下面我无法工作的示例所示。 我通常会有一个静态int变量,该变量会在程序运行时初始化为0,然后,如果某个方法捕获到异常,它将使int递增,并且每个方法仅在int为0时才运行。 这行得通,但我只是想知道是否可以用异常处理替换int shindig。 问题答案