跳至主要內容

异常传播

guodongAndroid大约 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
—--

我们分析下示例代码:

  1. 你必须明确选择使用 GlobalScope,因为它被标记为 DelicateCoroutinesApi。IDE 会警告你这一点。
  2. 你使用 launch 协程构建器启动了一个协程并在其中抛出了一个 IndexOutOfBoundsException。这是一个正常的异常传播示例。Thread.UncaughExceptionHandler 的默认实现处理了这个异常。在这种情况下,它只是简单的将错误打印为输出的一部分。
  3. join() 函数使协程挂起并等待工作完成。在这种情况下, launchJob 在异常情况下完成。
  4. 你使用 async 构建器启动了一个新的协程并在其中抛出一个 ArithmeticException 。在这种情况下什么都没有打印,因为这个协程是通过 async 启动的。它依赖用户调用 await() 函数。
  5. 为了捕获和处理异常,你在 deferred 对象上调用 await(),并将其包装在 try/catch 块中。请注意,IDE 在这里对你有所帮助。它识别你调用了 await(),并且发生了异常。正因为如此,IDE 给你一个警告,即 println() 语句无法执行。

尝试着注释掉 deferred.await() 的调用,看看会发生什么。异常不会发生并且 4. 也无法执行,此语句永远不会执行打印。