异常传播
大约 2 分钟
异常处理在协程中相当简单。如果代码抛出异常,环境就会传播异常,而无需你做任何事情。协程让异步的代码看起来像同步的。这样,你就可以像在同步代码中使用 try/catch
块一样处理异常。
你可以以多种方式构建一个协程。你使用的协程构建器决定了异常将如何传播以及如何处理异常。
- 当使用
launche
协程构建器时,异常发生时立即抛出并传播到父协程。异常被视为未捕获的异常,类似于Java 的Thread.UncaughExceptionHandler
。 - 当使用
async
作为根协程构建器时,异常仅在你调用await()
时抛出。
理解异常是如何传播的有助于找到正确处理它们的方法。
让我们编写一个简单的示例,在 GlobalScope 中创建新的协程,并从不同的协程构建器抛出异常。在 IntelliJ 中前往 kco-materials/08-exception-handing/projects/starter 目录并打开 ExceptionHanding 项目。打开 CoroutineExceptionHandlingExample.kt 文件并用以下代码替换其中的代码:
// 1
@OptIn(DelicateCoroutinesApi::class)
fun main() = runBlocking {
// 2
val launchJob = GlobalScope.launch {
println("1. Exception created via launch coroutine")
throw IndexOutOfBoundsException()
}
// 3
launchJob.join()
println("2. Joined failed job")
// 4
val deferred = GlobalScope.async {
println("3. Exception created via async coroutine")
throw ArithmeticException()
}
// 5
try {
deferred.await()
println("4. Unreachable, this statement is never executed")
} catch (e: Exception) {
println("5. Caught ${e.javaClass.simpleName}")
}
}
输出:
1. Exception created via launch coroutine
2. Joined failed job
3. Exception created via async coroutine
5. Caught ArithmeticException
Exception in thread "DefaultDispatcher-worker-1" java.lang.IndexOutOfBoundsException
—--
我们分析下示例代码:
- 你必须明确选择使用
GlobalScope
,因为它被标记为DelicateCoroutinesApi
。IDE 会警告你这一点。 - 你使用
launch
协程构建器启动了一个协程并在其中抛出了一个IndexOutOfBoundsException
。这是一个正常的异常传播示例。Thread.UncaughExceptionHandler
的默认实现处理了这个异常。在这种情况下,它只是简单的将错误打印为输出的一部分。 join()
函数使协程挂起并等待工作完成。在这种情况下,launchJob
在异常情况下完成。- 你使用
async
构建器启动了一个新的协程并在其中抛出一个ArithmeticException
。在这种情况下什么都没有打印,因为这个协程是通过async
启动的。它依赖用户调用await()
函数。 - 为了捕获和处理异常,你在
deferred
对象上调用await()
,并将其包装在try/catch
块中。请注意,IDE 在这里对你有所帮助。它识别你调用了await()
,并且发生了异常。正因为如此,IDE 给你一个警告,即println()
语句无法执行。
尝试着注释掉 deferred.await()
的调用,看看会发生什么。异常不会发生并且 4. 也无法执行,此语句永远不会执行打印。