Mar*_*nik 8 kotlin kotlin-coroutines
这段代码:
fun main() {
runBlocking {
try {
val deferred = async { throw Exception() }
deferred.await()
} catch (e: Exception) {
println("Caught $e")
}
}
println("Completed")
}
Run Code Online (Sandbox Code Playgroud)
结果输出:
Caught java.lang.Exception
Exception in thread "main" java.lang.Exception
at org.mtopol.TestKt$main$1$deferred$1.invokeSuspend(test.kt:11)
...
Run Code Online (Sandbox Code Playgroud)
这种行为对我没有意义.异常被捕获并处理,但它仍然作为未处理的异常逃到顶层.
这种行为是否有记录和预期?它违反了我对异常处理如何工作的所有直觉.
我从Kotlin论坛的一个帖子中改编了这个问题.
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")
}
Run Code Online (Sandbox Code Playgroud)
输出现在
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
Run Code Online (Sandbox Code Playgroud)
这再次不是我想要的行为.在这里,一个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())
}
Run Code Online (Sandbox Code Playgroud)
因为a是我们碰巧等待的第一个结果,此代码将继续运行 10 秒,然后导致错误并且没有实现任何有用的工作。在大多数情况下,我们希望在一个组件出现故障时立即取消所有内容。我们可以这样做:
val (a, b) = awaitAll(deferredA, deferredB)
println(a + b)
Run Code Online (Sandbox Code Playgroud)
这段代码不太优雅:我们被迫在同一个地方等待所有结果,我们失去了类型安全,因为awaitAll返回所有参数的公共超类型的列表。如果我们有一些
suspend fun suspendFun(): Int {
delay(10_000)
return 2
}
Run Code Online (Sandbox Code Playgroud)
我们想写
val c = suspendFun()
val (a, b) = awaitAll(deferredA, deferredB)
println(a + b + c)
Run Code Online (Sandbox Code Playgroud)
我们被剥夺了在suspendFun完成之前纾困的机会。我们可能会这样解决:
val deferredC = async { suspendFun() }
val (a, b, c) = awaitAll(deferredA, deferredB, deferredC)
println(a + b + c)
Run Code Online (Sandbox Code Playgroud)
但这很脆弱,因为您必须注意确保对每个可挂起的调用都执行此操作。这也违背了 Kotlin 的“默认顺序”原则
总而言之:当前的设计虽然起初违反直觉,但作为一种实用的解决方案确实有意义。它还加强了不使用的规则,async-await除非您正在对任务进行并行分解。
| 归档时间: |
|
| 查看次数: |
887 次 |
| 最近记录: |