我想捕获异步协程引发的异常。以下代码演示了一个问题:
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 two
的Deferred
的协同程序同时运行的协同程序中,因此在由于子协同程序的失败而被取消之前,它没有机会这样做。
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
}