在Kotlin异步协程中捕获异常并停止传播



我想捕获异步协程引发的异常。以下代码演示了一个问题:

import kotlinx.coroutines.*
fun main() = runBlocking<Unit> {
try {
println(failedConcurrentSum())
} catch (e: ArithmeticException) {
println("Computation failed with ArithmeticException")
}
}
suspend fun failedConcurrentSum() = coroutineScope {
try {
val one = async {
try {
delay(1000L)
42
} finally {
println("First child was cancelled")
}
}
val two = async<Int> {
println("Second child throws an exception")
throw ArithmeticException()
}
one.await() + two.await()
} catch (e: ArithmeticException) {
println("Using a default value...")
0
}
}

此打印:

Second child throws an exception
First child was cancelled
Computation failed with ArithmeticException

failedConcurrentSum内部的try-catch不处理val two引发的异常。我可以说服自己,这是由于"结构化并发"。

然而,这并不能解释为什么将async封装在coroutineScope中会捕获异常:

suspend fun failedConcurrentSum() = coroutineScope {
try {
val one = coroutineScope {
async {
try {
delay(1000L)
42
} finally {
println("First child was cancelled")
}
}
}
val two = coroutineScope {
async<Int> {
println("Second child throws an exception")
throw ArithmeticException()
}
}
one.await() + two.await()
} catch (e: ArithmeticException) {
println("Using a default value...")
0
}
}

此打印:

First child was cancelled
Second child throws an exception
Using a default value...
0

为什么后者捕获异常而第一个没有?

coroutineScope只是一个函数,它在内部设置其作用域,从外部来看,它总是像常规函数一样完成,不会干扰任何外部作用域。这是因为它不会泄漏在其范围内启动的任何并发协程。您总是可以可靠地捕获和处理coroutineScope抛出的异常。

另一方面,async在启动协同程序后立即完成,因此您有两个并发的协同程序:一个运行async代码,另一个调用相应的await。由于async也是调用await的一个的子级,因此它的失败会在父级的await调用完成之前取消父级。

failedConcurrentSum内部的try-catch不处理val two抛出的异常。

如果有机会的话,它确实会。但是,由于try-catch块在与完成val twoDeferred的协同程序同时运行的协同程序中,因此在由于子协同程序的失败而被取消之前,它没有机会这样做。

coroutineScope使用Job

默认情况下,作业的任何子项的失败都会导致其父项立即失败,并取消其余子项。作业

您可以使用supervisorScope而不是coroutineScope

子级的失败或取消不会导致主管作业失败,也不会影响其其他子级。主管工作

但是您必须等待第一个async块的完成。

当发生异常时,使用try catch中的coroutineScope立即返回默认值

suspend fun failedConcurrentSum() = try {
coroutineScope {
val one = async {
try {
delay(1000L)
42
} finally {
println("First child was cancelled")
}
}
val two = async<Int> {
println("Second child throws an exception")
throw ArithmeticException()
}
one.await() + two.await()
}
} catch (e: ArithmeticException) {
println("Using a default value...")
0
}

最新更新