父Job取消時(shí)如何取消子Job
fun main() {
//創(chuàng)建一個(gè)Job,當(dāng)然你也可以啟動(dòng)一個(gè)協(xié)程后返回
val job = GlobalScope.launch {
//啟動(dòng)一個(gè)子協(xié)程
launch {
Thread.sleep(200)
println("子協(xié)程完成")
}
Thread.sleep(100)
println("父協(xié)程完成")
}
job.cancel()
TimeUnit.SECONDS.sleep(1)
println("結(jié)束")
}
父協(xié)程完成
結(jié)束
我們看下子協(xié)程如何被取消的象缀。
首先我們需要知道 子協(xié)程啟動(dòng)的時(shí)候會(huì)放一個(gè)監(jiān)聽(tīng)器到父親NodeList中
. 這是在監(jiān)聽(tīng)父協(xié)程么
也就是如下代碼:
public final override fun attachChild(child: ChildJob): ChildHandle {
return invokeOnCompletion(onCancelling = true, handler = ChildHandleNode(this, child).asHandler) as ChildHandle
}
當(dāng)我們的demo源碼調(diào)用時(shí):
最終會(huì)調(diào)用到:
private fun makeCancelling(cause: Any?): Any? {
var causeExceptionCache: Throwable? = null // lazily init result of createCauseException(cause)
loopOnState { state ->
when (state) {
is Finishing -> {
//...由于協(xié)程未完成所以到這
}
is Incomplete -> {
//當(dāng)前協(xié)程未完成再悼,所判斷走到這
//創(chuàng)建一個(gè)取消異常
val causeException = causeExceptionCache ?: createCauseException(cause).also { causeExceptionCache = it }
//當(dāng)前協(xié)程是否存活 這里顯然返回true
if (state.isActive) {
if (tryMakeCancelling(state, causeException)) return COMPLETING_ALREADY
} else {
//....
}
}
}
else -> return TOO_LATE_TO_CANCEL // already complete
}
}
}
跟進(jìn)tryMakeCancelling(state, causeException))
private fun tryMakeCancelling(state: Incomplete, rootCause: Throwable): Boolean {
//如果當(dāng)前state不是NodeList惶傻,那么講當(dāng)前狀態(tài)變?yōu)镹odeList
//在我們本案例當(dāng)中當(dāng)前state是ChildHandle所以要變?yōu)镹odeList
val list = getOrPromoteCancellingList(state) ?: return false
//變?yōu)閏ancelling狀態(tài)
val cancelling = Finishing(list, false, rootCause)
//切換狀態(tài)
if (!_state.compareAndSet(state, cancelling)) return false
//喚醒NodeList各個(gè)節(jié)點(diǎn)
notifyCancelling(list, rootCause)
return true
}
繼續(xù)
private fun notifyCancelling(list: NodeList, cause: Throwable) {
//空函數(shù)用于擴(kuò)展的一個(gè)回調(diào)
onCancelling(cause)
//回調(diào)這個(gè)NodeList上的所有監(jiān)聽(tīng),所以這類(lèi)會(huì)在這里取消
notifyHandlers<JobCancellingNode<*>>(list, cause)
//取消父協(xié)程
cancelParent(cause)
}
notifyHandlers<JobCancellingNode<*>>(list, cause)
比較簡(jiǎn)單,這里不在做說(shuō)明。
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) }
}
以上我們知道了父協(xié)程取消的時(shí)候是通過(guò)NodeList取消子協(xié)程的.而子協(xié)程收到取消后悔遞歸的取消子協(xié)程和自身代碼谆扎。具體協(xié)程的取消細(xì)節(jié)后續(xù)章節(jié)在做說(shuō)明,這里只需要管如何傳遞取消的芹助。
我們最后看看父協(xié)程收到取消異常的之后的處理方法:
cancelParent(cause)
private fun cancelParent(cause: Throwable): Boolean {
//忽略 這里先當(dāng)做false堂湖,關(guān)于范圍協(xié)程后面有機(jī)會(huì)再說(shuō)
if (isScopedCoroutine) return true
val isCancellation = cause is CancellationException
val parent = parentHandle
if (parent === null || parent === NonDisposableHandle) {
return isCancellation
}
//最終調(diào)用了父類(lèi)的childCancelled函數(shù)
return parent.childCancelled(cause) || isCancellation
}
這個(gè)childCancelled
函數(shù)的聲明:
public interface ChildHandle : DisposableHandle {
@InternalCoroutinesApi
public fun childCancelled(cause: Throwable): Boolean
}
我們看下大致的兩種實(shí)現(xiàn):
//JobSupport.kt
public open fun childCancelled(cause: Throwable): Boolean {
//(CancellationException異常在手動(dòng)取消子類(lèi)的時(shí)候拋出)
//如果子類(lèi)不是由于意外異常取消的那么不取消父協(xié)程,
if (cause is CancellationException) return true
//如果子類(lèi)是由于取消異常之外的情況導(dǎo)致的,比如說(shuō)子協(xié)程除零異常的.那么取消父協(xié)程
return cancelImpl(cause) && handlesException
}
看下另一種子協(xié)程不管如何都不會(huì)取消父協(xié)程
private class SupervisorJobImpl(parent: Job?) : JobImpl(parent) {
override fun childCancelled(cause: Throwable): Boolean = false
}
也就是說(shuō)當(dāng)我們使用如下代碼子協(xié)程異常不會(huì)取消父協(xié)程(父協(xié)程依然可以取消子協(xié)程).
fun main() {
//創(chuàng)建一個(gè)Job
val job = GlobalScope.launch {
launch(SupervisorJob()) {
val d = 1 / 0
}
//為了讓子協(xié)程完成
delay(100)
println("協(xié)程完成")
}
TimeUnit.SECONDS.sleep(1)
println("結(jié)束")
}
輸出:
協(xié)程完成
結(jié)束
可見(jiàn)子協(xié)程異常之后父協(xié)程依然在運(yùn)行.否則就不會(huì)出現(xiàn)協(xié)程完成這個(gè)輸出.
我們總結(jié)下:
子協(xié)程
創(chuàng)建時(shí)會(huì)講自己放入父協(xié)程
的job
鏈表,所以當(dāng)父協(xié)程取消的時(shí)候會(huì)回調(diào)所有子協(xié)程.
子協(xié)程
取消時(shí)候會(huì)回調(diào)childCancelled
函數(shù),父協(xié)程根據(jù)情況判斷是否取消自己