Kotlin-协程核心库分析-Job父子取消

父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函数,父协程根据情况判断是否取消自己

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容