kotlin协程的异常传播体系具有独特性复杂性,本人写代码调试验证,传播规则如下
1、全是 Job 只使用 root,使用 parent 和 self 都无效,只在rootExceptionHandler被捕获
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}") }
//全是 Job 只使用 root,使用 parent 和 self 都无效,只在rootExceptionHandler被捕获
CoroutineScope(Job()).launch(rootExceptionHandler) {
launch(parentExceptionHandler) {
launch(selfExceptionHandler) {
throw Exception("子协程使用的是Job")
}
}
}
运行结果: 调用【根】协程异常处理器:子协程使用的是Job
这表明在全是普通job的情况下,任意子协程的设置异常捕获CoroutineExceptionHandler都会被忽略,异常直接传播到最顶层的根异常处理器,坑爹啊!!!。。。。
如果我就要当前的携程捕获,我应该怎么办呢?需要引入kotlin设计的监督者角色SupervisorJob,接着看2
2、从下往上在遇到 SupervisorJob 或 supervisorScope() 起,使用最近的那个异常处理器
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}") }
//即child无效,有self用self,没有self用parent,没有parent用root,同时设置self、parent、root用最近的self
CoroutineScope(Job()).launch(rootExceptionHandler) {
launch(SupervisorJob()+parentExceptionHandler) {
launch(SupervisorJob()+selfExceptionHandler) {
launch(childExceptionHandler) {
throw Exception("子协程使用的是SupervisorJob")
}
}
}
}
运行结果为: 调用【自身】协程异常处理器:子协程使用的是SupervisorJob
SupervisorJob()的功能就是充当了监管者,表示可以接管异常。
看一下他的实现,重写了childCancelled,如果异常被处理,则返回true,否则返回false(然后调用者负责处理异常),这里的意思调用者负责处理异常,也就是当前协程的CoroutineExceptionHandler
private class SupervisorJobImpl(parent: Job?) : JobImpl(parent) {
override fun childCancelled(cause: Throwable): Boolean = false
}
3、对于withContext调用,设置SupervisorJob无效
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(SupervisorJob()+parentExceptionHandler) {
withContext(Dispatchers.IO+ SupervisorJob()+selfExceptionHandler) {
withContext(Dispatchers.IO+SupervisorJob()+childExceptionHandler) {
throw Exception("子协程使用的是withContext")
}
}
}
}
运行结果:调用【父】协程异常处理器:子协程使用的是withContext
这里父协程设置了SupervisorJob()接管了异常。
withContext调用,设置SupervisorJob无效, 坑爹啊,widthContext挂起,只能对他的launch job使用SupervisorJob,这里设计其实也合理,如果启动在一个协程启动了withContext挂起当前去执行子协程,他里面发生了异常,那么恢复的时候,当前launch job就应该中断执行,那么总结就是withContext内部发生的异常,只能传播给最近的launch job(设置了SupervisorJob())负责。
如果都没有设置SupervisorJob,则异常传播到最顶级的根异常处理器