Job取消cancel过程分析
Job取消cancel过程非常复杂,尤其是多个父子Job嵌套,以及内部又拥有挂起函数的情况下,流程是相互依赖,相互等待的过程,所有这里只做最简单的Job情况,方便代码跟踪。
还是从一段最简单的代码开始跟踪,代码如下:
fun testJobTree() {
val myScope = CoroutineScope(CoroutineName("name") + IO + Job() + CoroutineExceptionHandler{ _, t -> })
val job = myScope.launch {
println("job has run to end")
}
println("job has launch")
job.cancel("cancel job0")
}
job的工作是打印一句话这个job的工作就完成了。在Job启动后,立刻调用cancel方法取消该Job。
我们从cancel方法跟踪进去,得到的流程如下:
当我们调用cancel方法之后,会依次进入:
cancel(cause: CancellationException?) -> cancelInternal(cause: Throwable) -> cancelImp(cause: Any?)
从cancelImp开始具体分析 (cancelImp这个方法比较重要,很多地方会调用,子Job通知父Job取消的方法,也会有可能让父Job去调用这个取消方法,parentHandler.childCancelled() -> parent.cancelImp())。
makeCancelling()代码如下:
internal fun cancelImpl(cause: Any?): Boolean {
var finalState: Any? = COMPLETING_ALREADY
if (onCancelComplete) {
// make sure it is completing, if cancelMakeCompleting returns state it means it had make it
// completing and had recorded exception
finalState = cancelMakeCompleting(cause)
if (finalState === COMPLETING_WAITING_CHILDREN) return true
}
if (finalState === COMPLETING_ALREADY) {
finalState = makeCancelling(cause)
}
return when {
finalState === COMPLETING_ALREADY -> true
finalState === COMPLETING_WAITING_CHILDREN -> true
finalState === TOO_LATE_TO_CANCEL -> false
else -> {
afterCompletion(finalState)
true
}
}
}
我们的代码不会出现上面判断的一个if (onCancelComplete)
条件成立的情况,onCancelComplete一般都是false,这个判断直接跳过,会来到makeCancelling方法:
cancel Job的过程中,job会存在以下四种状态:
// COMPLETING_ALREADY -- when already complete or completing
说明已经完成或者是正在完成
// COMPLETING_RETRY -- when need to retry due to interference
说明任务取消需要重试
// COMPLETING_WAITING_CHILDREN -- when made completing and is waiting for children
等待孩子Job结束,自己才能结束
// final state -- when completed, for call to afterCompletion
当结束后,等待调用afterCompletion回调通知
其中,如果Job处于Completing_Already或者是Completing_Waiting_children状态,多次取消cancel操作,将会被忽略,不会继续进行下面的取消的进一步操作。
makeCancelling方法会有三个分叉:
1.state是否是Finishing的状态,这种状态下直接调用:notifyCancelling(state.list, it)
2.state是InComplete的状态,然后还要判断是否是isActive的状态,
是活跃状态下,调用:tryMakeCancelling(state, causeException)
非活跃状态下,调用:tryMakeCompleting(state, CompletedExceptionally(causeException))
那么我们先分析第一种最简单的情况,notifyCancelling()方法,这个方法源码如下:
//JobSupport类
private fun notifyCancelling(list: NodeList, cause: Throwable) {
// first cancel our own children
onCancelling(cause)
notifyHandlers<JobCancellingNode>(list, cause)
// then cancel parent
cancelParent(cause) // tentative cancellation -- does not matter if there is no parent
}
第一个方法不重要,是需要子类去覆写的,是个空函数,
第二个方法是通知state的数据结构每个节点调用它的invoke方法,如果是ChildHandleNode节点的话,那么代理调用的是child.parentCancelled()方法,如果是其他节点的可能其他效果(这个节点类型后面再详解,有很多种);那么综上来看,这个方法就是父Job通知子Job,父Job已经取消了,子Job需要做对应的处理了、
第三个方法是Job需要通知并取消父Job、
//JobSupport类
private inline fun <reified T: JobNode> notifyHandlers(list: NodeList, cause: Throwable?) {
var exception: Throwable? = null
list.forEach<T> { node ->
try {
node.invoke(cause)
} catch (ex: Throwable) {
exception?.apply { addSuppressedThrowable(ex) } ?: run {
exception = CompletionHandlerException("Exception in completion handler $node for $this", ex)
}
}
}
exception?.let { handleOnCompletionException(it) }
}
可以看到上面的方法里面,是遍历state.list的数组,调用每个节点的invoke方法,其中node如果是ChildHandlerNode类型的话,是对应的子Job节点,看下这个类的结构:
internal class ChildHandleNode(
@JvmField val childJob: ChildJob
) : JobCancellingNode(), ChildHandle {
override val parent: Job get() = job
override fun invoke(cause: Throwable?) = childJob.parentCancelled(job)
override fun childCancelled(cause: Throwable): Boolean = job.childCancelled(cause)
}
触发的是invoke(cause: Throwable?) = childJob.parentCancelled(job)
,也就是通知子Job去取消。
//JobSupport类
private fun cancelParent(cause: Throwable): Boolean {
// Is scoped coroutine -- don't propagate, will be rethrown
if (isScopedCoroutine) return true
/* CancellationException is considered "normal" and parent usually is not cancelled when child produces it.
* This allow parent to cancel its children (normally) without being cancelled itself, unless
* child crashes and produce some other exception during its completion.
*/
val isCancellation = cause is CancellationException
val parent = parentHandle
// No parent -- ignore CE, report other exceptions.
if (parent === null || parent === NonDisposableHandle) {
return isCancellation
}
// Notify parent but don't forget to check cancellation
return parent.childCancelled(cause) || isCancellation
}
cancelParent()方法是取消父Job的作用,首先判断这个错误是不是取消异常(除了取消异常,还要其他抛出的Jvm异常等等),如果parent是空的,或者parent已经没有关联了,那么返回isCancellation的bol值,如果不是,那么调用parent.childCancelled()方法,返回 结果与isCancellation的bool值。
我们来看下childCancelled()方法:
job的类型有两种,一个是JobSupport也就是最常用的Job,还有一种是supervisorJob,这个覆写了childCancelled方法。
//JobSupport
public open fun childCancelled(cause: Throwable): Boolean {
if (cause is CancellationException) return true
return cancelImpl(cause) && handlesException
}
上面JobSupport内,childCancelled方法,对于来自子Job的取消异常CancellationException是不会让父job取消的,直接返回true,说明消耗了事件但是不取消自己;而Job的其他异常是会调用父Job自己的cancelImp方法,也就是会让父Job取消。
private class SupervisorJobImpl(parent: Job?) : JobImpl(parent) {
override fun childCancelled(cause: Throwable): Boolean = false
}
上面的supervisorJob是覆写了这个方法,直接返回false,也就是说,来自子Job的取消异常和其他异常都不会让父Job取消自己的,返回false代表父Job没有处理,不消耗这个通知事件,需要子Job自己去处理。
通过这个方法可以看出来,父Job无论是job还是supervisorJob都是不处理cancelledException的,其他的异常的话,普通父Job会被取消,supervisorJob会无视这个异常不处理,抛回给子Job自己处理。
我们来做个代码演示:
val myScope = CoroutineScope(CoroutineName("name") + IO + Job() + CoroutineExceptionHandler{ c, e -> println(e.message)})
val job = myScope.launch {
var job2 : Job? = launch {
println("job2 begin")
delay(1000)
println("job2 end")
}
var job3 : Job? = launch {
println("job3 begin")
delay(2000)
println("job3 end")
}
job2?.cancel("cancel job")
println("job has run to end")
}
println("job has launch")
按照刚刚分析的代码来看,job3的打印输出是不被job2的取消打断的,打印结果如下:
2022-11-15 15:46:17.608 E/MainActivity: main : job has launch
2022-11-15 15:46:17.611 E/MainActivity: DefaultDispatcher-worker-2 : job2 begin
2022-11-15 15:46:17.613 E/MainActivity: DefaultDispatcher-worker-1 : job has run to end
2022-11-15 15:46:17.614 E/MainActivity: DefaultDispatcher-worker-1 : job3 begin
2022-11-15 15:46:19.619 E/MainActivity: DefaultDispatcher-worker-2 : job3 end
确实如此,除了Job2自己被取消了,其他Job都没有受到影响。job2是要delay去等待是为了协程可以去检测当前状态是否取消,sleep是线程级别的,协程是没有办法在这里去检查的。
在看一个抛出异常的例子:
val myScope = CoroutineScope(CoroutineName("name") + IO + Job() + CoroutineExceptionHandler{ c, e ->
println(c[CoroutineName].toString() + e.message)
})
val job = myScope.launch {
var job2 : Job? = launch {
println("job2 begin")
delay(100)
throw Exception("测试异常报错")
println("job2 end")
}
var job3 : Job? = launch {
println("job3 begin")
delay(2000)
println("job3 end")
}
println("job has run to end")
}
println("job has launch")
打印的结果是:
2022-11-15 15:49:09.338 E/MainActivity: main : job has launch
2022-11-15 15:49:09.342 E/MainActivity: DefaultDispatcher-worker-1 : job has run to end
2022-11-15 15:49:09.343 E/MainActivity: DefaultDispatcher-worker-1 : job2 begin
2022-11-15 15:49:09.345 E/MainActivity: DefaultDispatcher-worker-4 : job3 begin
2022-11-15 15:49:09.448 E/MainActivity: DefaultDispatcher-worker-7 : CoroutineName(name)测试异常报错
可以看出来,job2,job3都启动成功了,但是随着job2的抛出异常,job2,job3的最后一条语句都没有打印,说明异常抛出的话,如果父Job是普通的Job,那么父Job会取消,父Job也会通知它的所有子Job取消。
再来看一个supervisorJob的例子:
val myScope = CoroutineScope(CoroutineName("name") + IO + Job() + CoroutineExceptionHandler{ c, e ->
println("CoroutineScope exceptionHandler: " + c[CoroutineName].toString() + e.message)
})
val job = myScope.launch {
var job2 : Job? = launch(SupervisorJob(coroutineContext[Job])) {
println("job2 begin")
delay(100)
throw Exception("测试异常报错")
println("job2 end")
}
var job3 : Job? = launch {
println("job3 begin")
delay(2000)
println("job3 end")
}
println("job has run to end")
}
println("job has launch")
打印的结果如下:
coroutineTestActivity: job has launch
job2 begin
coroutineTestActivity: job has run to end
job3 begin
coroutineTestActivity: CoroutineScope exceptionHandler: CoroutineName(name)测试异常报错
job3 end
可以看到Job2虽然抛出了异常,但是Job3还是完整的跑完了,没有中断,说明supervisorJob起到作用了,上面分析了,SupervisorJob遇到子Job的childCancelled方法回调是不处理的,所以只会在内部自己处理异常。
我们是在launch方法中传入了supervisorJob参数:
var job2 : Job? = launch(SupervisorJob(coroutineContext[Job])) {
println("job2 begin")
delay(100)
throw Exception("测试异常报错")
println("job2 end")
}
看下SupervisorJob(coroutineContext[Job])
这个构造函数做了什么:
public fun SupervisorJob(parent: Job? = null) : CompletableJob = SupervisorJobImpl(parent)
private class SupervisorJobImpl(parent: Job?) : JobImpl(parent) {
override fun childCancelled(cause: Throwable): Boolean = false
}
internal open class JobImpl(parent: Job?) : JobSupport(true), CompletableJob {
init { initParentJob(parent) } //这个函数上一章分析过,是关联父子Job的
//省略、、、
}
SupervisorJob -> SupervisorJobImpl -> JobImpl.initParentJob(parent)
我们直接看initParentJob(parent) 这个方法,这个方法我们在上一章 解析launch启动过程已经分析过了,这个方法是让parent和本身Job进行父子关联。然后supervisorJob作为参数传入launch方法,那么supervisorJob又和Job2产生了父子关系。这样我们就知道了例子中的代码的Job树的结构是怎么样的了:
那么Job2如果抛出异常的话,这个异常会被supervisorJob忽略掉,其他Job不会收到影响。
Job的异常处理
job的异常处理是和上面一节内容相关的,我们已经分析过了,SupervisorJob不会处理来自子Job的异常情况,那么这个来自子Job的异常情况该由谁来处理呢?我们从fun finalizeFinishingState(state: Finishing, proposedUpdate: Any?): Any?
这个方法开始分析:
// Finalizes Finishing -> Completed (terminal state) transition.
// ## IMPORTANT INVARIANT: Only one thread can be concurrently invoking this method.
// Returns final state that was created and updated to
private fun finalizeFinishingState(state: Finishing, proposedUpdate: Any?): Any? {
/*
* Note: proposed state can be Incomplete, e.g.
* async {
* something.invokeOnCompletion {} // <- returns handle which implements Incomplete under the hood
* }
*/
//----------------------检查当前state的状态----------------------------
assert { this.state === state } // consistency check -- it cannot change
assert { !state.isSealed } // consistency check -- cannot be sealed yet
assert { state.isCompleting } // consistency check -- must be marked as completing
val proposedException = (proposedUpdate as? CompletedExceptionally)?.cause
// Create the final exception and seal the state so that no more exceptions can be added
//----------------------收集错误信息----------------------------------
var wasCancelling = false // KLUDGE: we cannot have contract for our own expect fun synchronized
val finalException = synchronized(state) {
wasCancelling = state.isCancelling
val exceptions = state.sealLocked(proposedException)
val finalCause = getFinalRootCause(state, exceptions)
if (finalCause != null) addSuppressedExceptions(finalCause, exceptions)
finalCause
}
// Create the final state object
val finalState = when {
// was not cancelled (no exception) -> use proposed update value
finalException == null -> proposedUpdate
// small optimization when we can used proposeUpdate object as is on cancellation
finalException === proposedException -> proposedUpdate
// cancelled job final state
else -> CompletedExceptionally(finalException)
}
//----------------------异常处理----------------------------------
// Now handle the final exception
if (finalException != null) {
val handled = cancelParent(finalException) || handleJobException(finalException)
if (handled) (finalState as CompletedExceptionally).makeHandled()
}
// Process state updates for the final state before the state of the Job is actually set to the final state
// to avoid races where outside observer may see the job in the final state, yet exception is not handled yet.
if (!wasCancelling) onCancelling(finalException)
onCompletionInternal(finalState)
// Then CAS to completed state -> it must succeed
val casSuccess = _state.compareAndSet(state, finalState.boxIncomplete())
assert { casSuccess }
// And process all post-completion actions
completeStateFinalization(state, finalState)
return finalState
}
我们直接看异常处理部分代码,其他部分代码可以暂时不用关心,先调用了cancelParent(finalException)
方法,根据上面的分析画出流程图:
最后异常会由Job自己进行处理,调用了handleJobException方法进行处理:
private open class StandaloneCoroutine(
parentContext: CoroutineContext,
active: Boolean
) : AbstractCoroutine<Unit>(parentContext, initParentJob = true, active = active) {
override fun handleJobException(exception: Throwable): Boolean {
handleCoroutineException(context, exception)
return true
}
}
@InternalCoroutinesApi
public fun handleCoroutineException(context: CoroutineContext, exception: Throwable) {
// Invoke an exception handler from the context if present
try {
context[CoroutineExceptionHandler]?.let {
it.handleException(context, exception)
return
}
} catch (t: Throwable) {
handleCoroutineExceptionImpl(context, handlerException(exception, t))
return
}
// If a handler is not present in the context or an exception was thrown, fallback to the global handler
handleCoroutineExceptionImpl(context, exception)
}
可以看到调用的是被StandaloneCoroutine覆写的handleJobException方法 -> handleCoroutineException方法,里面处理方式是:先取出这个协程的上下文集合中的ExceptionHandle来对异常进行处理,这个context定义如下:
public final override val context: CoroutineContext = parentContext + this
launch的时候,继承自scopeCoroutine的上下文,然后用自己Job类型去覆盖集合中的元素。如果这个context[CoroutineExceptionHandler]
不存在的话,那么下面还需要调用handleCoroutineExceptionImpl
方法,代码如下:
internal actual fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable) {
// use additional extension handlers
for (handler in handlers) {
try {
handler.handleException(context, exception)
} catch (t: Throwable) {
// Use thread's handler if custom handler failed to handle exception
val currentThread = Thread.currentThread()
currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, handlerException(exception, t))
}
}
// use thread's handler
val currentThread = Thread.currentThread()
// addSuppressed is never user-defined and cannot normally throw with the only exception being OOM
// we do ignore that just in case to definitely deliver the exception
runCatching { exception.addSuppressed(DiagnosticCoroutineContextException(context)) }
currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, exception)
}
遍历一下已经添加到handler列表里面的异常处理器用来处理异常,如果没有会调用当前线程的异常处理器,这部分大概就是这样。
在supervisorJob的例子中,我们发现Job2抛出异常后,异常被myScope异常处理器处理,说明Job2的上下文集合中继承了myScope集合的ExceptionHandler元素,因为supervisorJob只是和父Job关联了,并且launch方法从父Job那里继承元素集合,而我们的Job2是又和supervisorJob组成父子关系,又拥有ExceptionHandler,因此异常发生的时候会用继承过来的异常处理器进行异常处理,如果我们在launch的参数中添加ExceptionHandler话,运行下看下效果:
var job2 : Job? = launch(SupervisorJob(coroutineContext[Job]) + CoroutineExceptionHandler{ c, e ->
println("job2 exceptionHandler: " + c[CoroutineName].toString() + e.message)
}) {
println("job2 begin")
delay(100)
throw Exception("测试异常报错")
println("job2 end")
}
打印结果:
2022-11-15 17:22:15.989 E/MainActivity: main : job has launch
2022-11-15 17:22:15.995 E/MainActivity: DefaultDispatcher-worker-1 : job has run to end
2022-11-15 17:22:15.996 E/MainActivity: DefaultDispatcher-worker-1 : job2 begin
2022-11-15 17:22:15.998 E/MainActivity: DefaultDispatcher-worker-1 : job3 begin
2022-11-15 17:22:16.099 E/MainActivity: DefaultDispatcher-worker-2 : job2 exceptionHandler: CoroutineName(name)测试异常报错
2022-11-15 17:22:18.002 E/MainActivity: DefaultDispatcher-worker-2 : job3 end
可以看到Job2的异常被自己添加的job2 exceptionHandler处理了,因为i从父继承过来的ExceptionHandler被自己设置的ExceptionHandler覆盖掉了,其他的运行效果也不变。
Job异常处理的传递链条
首先分析如下代码:
val myScope = CoroutineScope(CoroutineName("name") + IO + Job() + CoroutineExceptionHandler{ c, e ->
println("CoroutineScope exceptionHandler: " + c[CoroutineName].toString() + e.message)
})
val job = myScope.launch(CoroutineExceptionHandler{ c, e ->
println("job exceptionHandler: " + c[CoroutineName].toString() + e.message)
}) {
var job2 : Job? = launch(CoroutineExceptionHandler{ c, e ->
println("job2 exceptionHandler: " + c[CoroutineName].toString() + e.message)
}) {
println("job2 begin")
delay(100)
throw Exception("测试异常报错")
println("job2 end")
}
var job3 : Job? = launch {
println("job3 begin")
delay(2000)
println("job3 end")
}
println("job has run to end")
}
println("job has launch")
打印的结果是:
2022-11-15 17:29:27.926 E/MainActivity: main : job has launch
2022-11-15 17:29:27.934 E/MainActivity: DefaultDispatcher-worker-1 : job has run to end
2022-11-15 17:29:27.935 E/MainActivity: DefaultDispatcher-worker-1 : job2 begin
2022-11-15 17:29:27.937 E/MainActivity: DefaultDispatcher-worker-3 : job3 begin
2022-11-15 17:29:28.040 E/MainActivity: DefaultDispatcher-worker-2 : job exceptionHandler: CoroutineName(name)测试异常报错
可以看到,异常处理被Job处理了, 很好奇,为啥不是根部的scope的Job去处理的异常处理啊,卧槽,还得继续跟踪代码,关键还是这个函数fun finalizeFinishingState(state: Finishing, proposedUpdate: Any?): Any?
的异常处理的部分:
//JobSupport
// Now handle the final exception
if (finalException != null) {
val handled = cancelParent(finalException) || handleJobException(finalException)
if (handled) (finalState as CompletedExceptionally).makeHandled()
}
//JobSupport
public open fun childCancelled(cause: Throwable): Boolean {
if (cause is CancellationException) return true
return cancelImpl(cause) && handlesException
}
首先会调用cancelParent(finalException)
尝试让父Job调用childCancelled
方法处理异常,这个函数返回false,说明父Job不处理,就会让Job本身去处理异常,job
协程和myScope的Job
组成了父子关系,进而会尝试让myScope的Job
处理异常,但是myScope的Job
的handlesException
的字段是false,导致返回的值是false:
internal open val handlesException: Boolean get() = true
override val handlesException: Boolean = handlesException()
@JsName("handlesExceptionF")
private fun handlesException(): Boolean {
var parentJob = (parentHandle as? ChildHandleNode)?.job ?: return false
while (true) {
if (parentJob.handlesException) return true
parentJob = (parentJob.parentHandle as? ChildHandleNode)?.job ?: return false
}
}
mySocpe的handlesException
这个字段为啥是false呢?作为根Job,所以它没有父Job,(parentHandle as? ChildHandleNode)?.job ?: return false
它的parentHandle必然是空的,因此导致返回false。这样的话myScope的Job
不处理异常,cancelParent(finalException)
函数返回false,导致只能job
自己去处理异常调用了handleJobException(finalException)
函数,最后进入了job
自己的异常处理器中。
再举一个例子,让job
协程不定义自己的异常处理器,看看是哪个处理了异常:
val myScope = CoroutineScope(CoroutineName("name") + IO + Job() + CoroutineExceptionHandler{ c, e ->
printlnM("CoroutineScope exceptionHandler: " + c[CoroutineName].toString() + e.message)
})
val job = myScope.launch {
var job2 : Job? = launch(CoroutineExceptionHandler{ c, e ->
printlnM("job2 exceptionHandler: " + c[CoroutineName].toString() + e.message)
}) {
printlnM("job2 begin")
delay(100)
throw Exception("测试异常报错")
printlnM("job2 end")
}
var job3 : Job? = launch {
printlnM("job3 begin")
delay(2000)
printlnM("job3 end")
}
printlnM("job has run to end")
}
printlnM("job has launch")
2022-11-15 23:15:12.989 E/coroutineTestActivity: job has launch
2022-11-15 23:15:12.990 E/coroutineTestActivity: job has run to end
2022-11-15 23:15:12.990 E/coroutineTestActivity: job3 begin
2022-11-15 23:15:12.990 E/coroutineTestActivity: job2 begin
2022-11-15 23:15:13.092 E/coroutineTestActivity: CoroutineScope exceptionHandler: CoroutineName(name)测试异常报错
可以看到是myScope
exceptionHandler处理的异常,这是因为job
launch的时候,继承的就是Scope的上下文元素集合,没有传入CoroutineExceptionHandler
元素,所以myScope
exceptionHandler也不会被覆盖掉,这样job
在处理异常的时候使用的myScope
exceptionHandler来处理的异常。
使用范例
想让协程A,B异常互不影响的写法。
- 正确姿势一:
val scope = CoroutineScope(SupervisorJob() + CoroutineExceptionHandler { _, _ -> })
scope.launch(CoroutineName("A")) {
delay(10)
throw RuntimeException()
}
scope.launch(CoroutineName("B")) {
delay(100)
Log.e("petterp", "正常执行,我不会收到影响")
}
- 正确姿势二:
supervisorScope {
launch(CoroutineName("A")) {
printlnM("job2 begin")
delay(100)
throw Exception("测试异常报错")
printlnM("job2 end")
}
launch(CoroutineName("B")) {
printlnM("job3 begin")
delay(2000)
printlnM("job3 end")
}
}
上面代码的节点结构:
- 错误姿势:
val scope = CoroutineScope(CoroutineExceptionHandler { _, _ -> })
scope.launch(CoroutineName("job") + SupervisorJob()) {
launch(CoroutineName("A")) {
delay(10)
throw RuntimeException()
}
launch(CoroutineName("B")) {
delay(100)
Log.e("petterp", "正常执行,我不会收到影响")
}
}
scope.launch方法会创建一个job,参数中的SupervisorJob只是覆盖context集合中的父job元素,当前的ob和SupervisorJob仍然会构成父子关系,因此子A和B不是直接和SupervisorJob连接的,异常传递还是会到达job,因此A和B异常会相互影响。
上面节点结构:
总结
-
异常的传导链,从子Job向父Job传导:如果父Job是supervisorJob的话,将不做处理,需要子Job自己处理;如果父Job是JobSupport的话,异常还会继续向父Job的父Job传递,直到根部Job。所以捕获异常的话在根ScopeCoroutine里面设置就比较合适,对于有supervisorJob的情况,需要在supervisorJob的子Job中设置异常处理器,supervisorJob自己不会处理异常。
异常的情况:
-
对应协程取消操作,子Job取消,不论父Job是supportJob还是supervisorJob,父Job都不会处理cancelledException,父Job不受影响;子Job会主动取消自己以及自己的所有子Job。
取消的情况:
异常的处理逻辑可以用职场的例子解释。假设职场的潜规则是,任何员工出错了,首要是要向上级报告,如果上级愿意处理你的错误,那员工就不用管了,如果上级将问题打回给员工,那错误就得由员工自己处理
那么回到问题本身,Job就相当于一个好老板,子协程犯的错,它愿意处理,SupervisorJob就相当于一个严厉的老板,子协程自己犯的错,自己解决。