0%

协程异常的传递与处理

结构化并发

让我们先将思路转为日常业务开发中,比如在某某业务中,可能存在好几个需要同时处理的逻辑,比如同时请求两个网络接口,同时操作两个子任务等。我们暂且称上述学术化概念为 多个并发操作。

而每个并发操作其实都是在处理一个单独的任务,这个任务中,可能还存在子任务 ; 同样对于这个子任务来说,它又是其父任务的子单元。每个任务都有自己的生命周期,子任务的生命周期会继承父任务的生命周期,比如如果父任务关闭,子任务也会被取消。而如果满足这样特性,我们就称其就是 结构化并发。

异常传递流程

默认情况下,任意一个协程发生异常时都会影响到整个协程树,而异常的传递通常是双向的,也即协程会向子协程与父协程共同传递。

整体流程如下:

  1. 先 cancel 子协程
  2. 取消自己
  3. 将异常传递给父协程
  4. (重复上述过程,直到根协程关闭)

异常传递形式

  • launch : 层层向上传递,在launch外部try-catch无效

  • async : 根协程启动或加SupervisorJob启动,会优先当前暴露,可在await()捕获;否则会将异常传递给父协程,导致异常没有在调用处暴露。

异常处理

是否是根协程启动

async + 是否是根协程启动

  • async 时内部也是新的作用域,如果 async 对应的是根协程,那么我们可以在 await() 时直接捕获异常
  • async 时如果不是根协程启动,则会将异常传递给父协程,导致异常没有在调用处暴露。可以在async时声明SupervisorJob,此时即可在 await() 时捕获异常

launch + 是否是根或父协程自带异常处理器

  • 在launch外部try-catch无用
  • launch自带的异常处理器不会处理当前协程发生的异常

SupervisorJob

根协程自带的SupervisorJob,子协程会继承

非根协程的父协程声明的SupervisorJob,子协程不会继承: 子协程在 launch 时会创建新的协程作用域,其会使用默认新的 Job 替代父协程传递 SupervisorJob ,

所以导致我们传递的 SupervisorJob 被覆盖。所以如果我们想让子协程不影响父协程或者其他子协程,此时就必须再显示添加 SupervisorJob。

场景实践

严格意义上来说,所有异常都可以用 tryCatch 去处理

严格意义上来说,所有异常都可以用 tryCatch 去处理,只要我们的处理位置得当。但这并不是所有方式的最优解,特别是如果你想更优雅的处理异常时,此时就可以考虑 CoroutineExceptionHandler 。

什么时候该用 SupervisorJob ,什么时候该用 Job?

引用官方的一句话就是:想要避免取消操作在异常发生时被传播,记得使用 SupervisorJob ;反之则使用 Job。

SupervisorJob + tryCatch

我们有两个接口 A,B 需要同时请求,当接口A异常时,需要不影响B接口的正常展示,当接口B异常时,此时界面展示异常信息

SupervisorJob + CoroutineExceptionHandler

如果你有一个顶级协程,并且需要自动捕获所有的异常,则此时可以选用上述方式

参考