协程进阶:异常 01. 异常传播的两种形式

本篇解析 example-exceptions-01.kt。探讨 launchasync 在异常处理上的本质差异。

1. 核心概念:自动传播 vs 向用户暴露

自动传播 (launch)

  • 行为:异常被视为“未捕获”的。它会立即向上传播给父协程,最终由 Thread.uncaughtExceptionHandlerCoroutineExceptionHandler 处理。
  • 特点:即使你 join 了这个任务,异常也会在后台静默抛出(并可能导致崩溃)。

向用户暴露 (async)

  • 行为:异常被封装在返回的 Deferred 对象中。
  • 特点:异常不会立即导致程序崩溃。只有当你显式调用 deferred.await() 时,异常才会被重新抛出。此时你可以使用 try-catch 来捕获它。

2. 代码解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// launch:报错立即向上传播
val job = GlobalScope.launch {
throw IndexOutOfBoundsException()
}

// async:报错被暂存,直到 await()
val deferred = GlobalScope.async {
throw ArithmeticException()
}
try {
deferred.await() // 此时捕获异常
} catch (e: ArithmeticException) {
println("Caught ArithmeticException")
}

3. 开发者感悟

理解这两者的区别至关重要。launch 适合“发射后不管”的后台任务;async 适合需要获取结果的任务。在 Android 中,如果你不希望一个后台请求失败导致整个应用崩溃,使用 async 并在 await 时处理异常是更安全的做法。

,