CancellationException异常是协程专门用来取消协程而使用,与其他异常在处理上是有区别的
- 协程内部使用CancellationException来进行取消操作,这个异常会被忽略掉。
- 当子协程取消后,不会取消父协程。(如果是其他异常,父协程也完蛋喽!)
- 如果一个协程遇到CancellationException以外的异常,它将使用该异常来取消父协程。当父协程和子协程都结束后,异常才会被父协程处理。
下面的例子中,println("异常捕获 ${exception.message}")这句话是最后被执行的,因为上面的第三点提到,父协程处理异常是要在所有的子协程全部都执行完毕。
step1:当第二个子协程抛出异常后,向父协程抛出CancellationException来取消父协程。
step2:父协程给第一个子协程传递一个异常,第一个子协程捕获到了异常,执行finally清理资源。(这里清理资源时必须使用withContext(NonCancellable)包裹, 因为这样才不会被其他异常打扰,完成资源清理工作)
step3:当子协程都执行完成后,这里就是第一个子协程的finally执行完毕后,才使用handler处理第二个子协程抛出的异常。
@Test
fun coroutineExceptionHandlerTest2() = runBlocking<Unit> {
var handler = CoroutineExceptionHandler{ _ , exception ->
println("异常捕获 ${exception.message}")
}
var job1 = GlobalScope.launch(handler) {
launch {
try {
println("第一个子协程进入")
delay(Long.MAX_VALUE)
} catch (e : java.lang.Exception) {
println("第一个子协程捕获到异常,${e.message}")
} finally {
withContext(NonCancellable) {
println("第一个子协程准备清理资源")
delay(5000)
println("第一个子协程准备清理资源完毕")
}
}
}
launch {
println("第二个子协程进入")
delay(1000)
throw ArithmeticException("抛出一个算数异常!!!!!!!!!")
}
}
job1.join()
}
第一个子协程进入
第二个子协程进入
第一个子协程捕获到异常,Parent job is Cancelling
第一个子协程准备清理资源
第一个子协程准备清理资源完毕
异常捕获 抛出一个算数异常!!!!!!!!!