Kotlin-協(xié)程核心庫(kù)分析-Job父子取消

父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ù)情況判斷是否取消自己

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市状土,隨后出現(xiàn)的幾起案子无蜂,更是在濱河造成了極大的恐慌,老刑警劉巖蒙谓,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件斥季,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)酣倾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)舵揭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人躁锡,你說(shuō)我怎么就攤上這事午绳。” “怎么了稚铣?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵箱叁,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我惕医,道長(zhǎng)耕漱,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任抬伺,我火速辦了婚禮螟够,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘峡钓。我一直安慰自己妓笙,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布能岩。 她就那樣靜靜地躺著寞宫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拉鹃。 梳的紋絲不亂的頭發(fā)上辈赋,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音膏燕,去河邊找鬼钥屈。 笑死,一個(gè)胖子當(dāng)著我的面吹牛坝辫,可吹牛的內(nèi)容都是我干的篷就。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼近忙,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼竭业!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起及舍,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤永品,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后击纬,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體鼎姐,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了炕桨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片饭尝。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖献宫,靈堂內(nèi)的尸體忽然破棺而出钥平,到底是詐尸還是另有隱情,我是刑警寧澤姊途,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布涉瘾,位于F島的核電站,受9級(jí)特大地震影響捷兰,放射性物質(zhì)發(fā)生泄漏立叛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一贡茅、第九天 我趴在偏房一處隱蔽的房頂上張望秘蛇。 院中可真熱鬧,春花似錦顶考、人聲如沸赁还。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)艘策。三九已至,卻和暖如春渊季,著一層夾襖步出監(jiān)牢的瞬間朋蔫,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工梭域, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人搅轿。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓病涨,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親璧坟。 傳聞我的和親對(duì)象是個(gè)殘疾皇子既穆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容