This code:

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)
    ...

这种行为对我来说毫无意义.异常被捕获并处理,但它仍然作为未处理的异常逃逸到顶级.

Is this behavior documented and expected? It violates all my intuitions on how exception handling is supposed to work.

我从Kotlin forum的一个帖子里改编了这个问题.


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")
}

The output is now

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

这又不是我想要的行为.在这里,一个launched协同路由失败,出现了一个未处理的异常,使其他协同路由的工作无效,但它们不间断地继续进行.

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

推荐答案

在研究了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)

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

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

and we want to write

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)

但这是脆弱的,因为你必须小心,确保你这样做的每一个暂停通话.这也违反了Kotlin 的"默认顺序"原则

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

Kotlin相关问答推荐

DataSnapshot.getValue()未记录的奇怪行为

Kotlin多平台(KMP)保存到文件不能在iOS上保存

如何在 Spring Boot 3 中为内部类提供运行时提示

奇怪的 cotlin check Not Null 参数错误

这是什么 Kotlin 类型:(String..String?)

在 APK META-INF/library_release.kotlin_module 中复制的重复文件

如何为你的 Flutter 元素添加 Kotlin 支持?

从 Java 调用 Kotlin 高阶函数

如何修复 ViewPager2 中的Design assumption violated错误?

Android Kotlin StringRes 数量String

无法为 retrofit2.Call 调用无参数构造函数

Kotlin JVM 和 Kotlin Native 有什么区别?

如何解决:将Java类转换为Kotlin后出现error: cannot find symbol class ...?

空对象引用上的 TransitionSet ArrayList.size()

Kotlin lambda 语法混淆

Kotlin Android:属性委托必须有一个 'getValue(DashViewModel, KProperty*>)' 方法

Kotlin内联属性的用例是什么?

kotlin中密封类和密封接口的区别是什么

如何在kotlin用mockito模仿lambda

项目不会使用 Kotlin 1.1.3 构建