一、异常的传播
- Job的取消和异常传播是双向的(结构化并发),如果抛异常的代码在局部没有捕获处理,协程或将其传播到层次结构中,该协程会先取消所有子协程再取消自己,如果这个异常是 CancellationException 类型便终止向上传播,如果使用了 SupervisorScope() 或 supervisorJob 不管什么类型的异常都终止向上传播,否则会一直传递到根协程导致整个结构中的协程都会被取消。
CancellationException类型的异常由抛出的协程处理,SupervisorJob类型时由该层的下级子协程处理(可能是抛异常的协程或它的父协程,但是都是SupervisorJob层的子协程),其他情况由根协程处理。
1.1 多个协程作用域之间的关系
| 类型 | 异常传播特征 | 场景举例 |
| 顶级作用域 | 不向外传播。 | 1.根协程之间。2.GlobalScope嵌套GlobalScope彼此独立互不影响。3.A和B是两个作用域对象,A开启的作用域中B开启了作用域,两个作用域彼此独立互不影响。4.supervisorScope() 或 supervisorJob 由于使用了新的 Job,相当于是一个独立的根协程,与外部互不影响。 |
| 协同作用域 | 双向传播。 | 外层有父协程,且自身非另外的作用域对象开启。 |
| 主从作用域 | 向下单向传播。 | 外层有父协程,自身是supervisorScope()或supervisorJob。与内部直接子协程主从,与外部协同。 |
CoroutineScopr(Dispatcher.IO).lacunch{}
二、打断传播
当不希望异常向上传播或兄弟协程相互影响时使用(向下传播依然存在)。SupervisorScope() 使用的 supervisorJob,使用新的 Job 开启协程作用域意味着它是独立的,因此只需要关心自己内部,异常不处理会导致程序崩溃。
2.1 supervisorJob
launch(SupervisorJob()) {
launch(SupervisorJob()) {
launch(SupervisorJob()) {
2.2 SupervisorScope()
三、CancellationException
如果异常是 CancellationException 及其子类,将不会向上传递,只取消当前协程及其子类。
object MyException : CancellationException()
suspend fun main(): Unit = coroutineScope {
四、异常捕获
| launch | 代码块中抛出的异常直接抛出。 |
| async | 代码块中抛出的异常通过最终消费即调用 await() 才抛出,子协程中的异常不受 await() 影响,未捕获会向上传递给根协程处理。所以对每个 await() 单独捕获是避免崩溃影响其它并发任务,再捕获全部 async 是避免子协程异常向上传递导致程序崩溃(也可以在外面套一层异常不向上传递的supervisorScope() 或 supervisorJob),或者使用CoroutineExceptionHandler。 |
4.1 try-catch
| 协程构建器 | 捕获协程构建器无效,要捕获构建器代码块中具体抛异常的代码:如果协程代码块中 throw 的异常没有被捕获处理,就会被协程框架(即BaseContinuationImpl.resumeWith()中)捕获封装成 Result 对象传递,最终传递给异常处理器,不会再次throw,也就没有异常可捕获了,也就是构建器不抛异常。 |
| 挂起函数 | 能捕获到挂起函数中子线程的异常:try捕获子线程是无效的,只能捕获当前线程的堆栈信息。在协程中能捕获到开启了子线程的挂起函数中的异常,是因为挂起函数底层代码通过 reusmeWithExceptoon() 携带异常从子线程恢复到当前线程抛出,不然直接 throw 是捕获不到的还会导致永远挂起。 |
4.2 CoroutineExceptionHandler
不会阻止异常传递,当执行时表示结构化并发已全部取消完成,是最后一次捕获异常。意思是无法从异常中恢复协程,只能用来做最后的处理(不捕获然就是线程的 UncaughtExceptionHandler 处理了),默认情况它会打印异常堆栈。
| 层次结构中的上下文 | 异常处理器设置在哪层生效 |
| 全是Job时 | 只有根协程设置了异常处理器才有效,父协程或自己设置了依旧崩。 |
| 有supervisorJob或SupervisorScope()时 | 从下往上,没遇到时设置了也没用,从遇到时起,不管设置在哪层或好几层都有设置,只有最近的那个生效。 |
fun main(): Unit = runBlocking {
val rootExceptionHandler = CoroutineExceptionHandler { _, throwable -> println("调用【根】协程异常处理器:${throwable.message}") }
val parentExceptionHandler = CoroutineExceptionHandler { _, throwable -> println("调用【父】协程异常处理器:${throwable.message}") }
val selfExceptionHandler = CoroutineExceptionHandler { _, throwable -> println("调用【自身】协程异常处理器:${throwable.message}") }
val childExceptionHandler = CoroutineExceptionHandler { _, throwable -> println("调用【子】协程异常处理器:${throwable.message}") }
CoroutineScope(Job()).launch(rootExceptionHandler) {
launch(parentExceptionHandler) {
launch(selfExceptionHandler) {
throw Exception("子协程使用的是Job")
CoroutineScope(Job()).launch(rootExceptionHandler) {
launch(parentExceptionHandler) {
launch(SupervisorJob() + selfExceptionHandler ) {
launch(childExceptionHandler) {
throw Exception("子协程使用的是SupervisorJob")