Kotlin-协程核心库分析-Job父子取消
2021-02-04 本文已影响0人
有腹肌的豌豆Z
父Job取消时如何取消子Job
fun main() {
//创建一个Job,当然你也可以启动一个协程后返回
val job = GlobalScope.launch {
//启动一个子协程
launch {
Thread.sleep(200)
println("子协程完成")
}
Thread.sleep(100)
println("父协程完成")
}
job.cancel()
TimeUnit.SECONDS.sleep(1)
println("结束")
}
父协程完成
结束
我们看下子协程如何被取消的。
首先我们需要知道 子协程启动的时候会放一个监听器到父亲NodeList中
. 这是在监听父协程么
也就是如下代码:
public final override fun attachChild(child: ChildJob): ChildHandle {
return invokeOnCompletion(onCancelling = true, handler = ChildHandleNode(this, child).asHandler) as ChildHandle
}
当我们的demo源码调用时:
最终会调用到:
private fun makeCancelling(cause: Any?): Any? {
var causeExceptionCache: Throwable? = null // lazily init result of createCauseException(cause)
loopOnState { state ->
when (state) {
is Finishing -> {
//...由于协程未完成所以到这
}
is Incomplete -> {
//当前协程未完成,所判断走到这
//创建一个取消异常
val causeException = causeExceptionCache ?: createCauseException(cause).also { causeExceptionCache = it }
//当前协程是否存活 这里显然返回true
if (state.isActive) {
if (tryMakeCancelling(state, causeException)) return COMPLETING_ALREADY
} else {
//....
}
}
}
else -> return TOO_LATE_TO_CANCEL // already complete
}
}
}
跟进tryMakeCancelling(state, causeException))
private fun tryMakeCancelling(state: Incomplete, rootCause: Throwable): Boolean {
//如果当前state不是NodeList,那么讲当前状态变为NodeList
//在我们本案例当中当前state是ChildHandle所以要变为NodeList
val list = getOrPromoteCancellingList(state) ?: return false
//变为cancelling状态
val cancelling = Finishing(list, false, rootCause)
//切换状态
if (!_state.compareAndSet(state, cancelling)) return false
//唤醒NodeList各个节点
notifyCancelling(list, rootCause)
return true
}
继续
private fun notifyCancelling(list: NodeList, cause: Throwable) {
//空函数用于扩展的一个回调
onCancelling(cause)
//回调这个NodeList上的所有监听,所以这类会在这里取消
notifyHandlers<JobCancellingNode<*>>(list, cause)
//取消父协程
cancelParent(cause)
}
notifyHandlers<JobCancellingNode<*>>(list, cause)
比较简单,这里不在做说明。
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) }
}
以上我们知道了父协程取消的时候是通过NodeList取消子协程的.而子协程收到取消后悔递归的取消子协程和自身代码。具体协程的取消细节后续章节在做说明,这里只需要管如何传递取消的。
我们最后看看父协程收到取消异常的之后的处理方法:
cancelParent(cause)
private fun cancelParent(cause: Throwable): Boolean {
//忽略 这里先当做false,关于范围协程后面有机会再说
if (isScopedCoroutine) return true
val isCancellation = cause is CancellationException
val parent = parentHandle
if (parent === null || parent === NonDisposableHandle) {
return isCancellation
}
//最终调用了父类的childCancelled函数
return parent.childCancelled(cause) || isCancellation
}
这个childCancelled
函数的声明:
public interface ChildHandle : DisposableHandle {
@InternalCoroutinesApi
public fun childCancelled(cause: Throwable): Boolean
}
我们看下大致的两种实现:
//JobSupport.kt
public open fun childCancelled(cause: Throwable): Boolean {
//(CancellationException异常在手动取消子类的时候抛出)
//如果子类不是由于意外异常取消的那么不取消父协程,
if (cause is CancellationException) return true
//如果子类是由于取消异常之外的情况导致的,比如说子协程除零异常的.那么取消父协程
return cancelImpl(cause) && handlesException
}
看下另一种子协程不管如何都不会取消父协程
private class SupervisorJobImpl(parent: Job?) : JobImpl(parent) {
override fun childCancelled(cause: Throwable): Boolean = false
}
也就是说当我们使用如下代码子协程异常不会取消父协程(父协程依然可以取消子协程).
fun main() {
//创建一个Job
val job = GlobalScope.launch {
launch(SupervisorJob()) {
val d = 1 / 0
}
//为了让子协程完成
delay(100)
println("协程完成")
}
TimeUnit.SECONDS.sleep(1)
println("结束")
}
输出:
协程完成
结束
可见子协程异常之后父协程依然在运行.否则就不会出现协程完成这个输出.
我们总结下:
子协程
创建时会讲自己放入父协程
的job
链表,所以当父协程取消的时候会回调所有子协程.
子协程
取消时候会回调childCancelled
函数,父协程根据情况判断是否取消自己