Kotlin协程上下文CoroutineContext是如何可相加的

假设你已经知道协程上下文CoroutineContext这个东西,并且知道它是可以相加的了

 

三个主要的类

CoroutineContext: 所有上下文的接口

CombinedContext:上下文组合时生成的类

CoroutineContext.Element:大部分单个上下文实现的类,因为有的会直接实现CoroutineContext

 

上下文相加的逻辑需要这三个类的方法配合实现。

其中,所有的行为逻辑都在CoroutineContext 接口中通过声明的方法定义好了。也就是说,当我们使用上下文时,只要面向CoroutineContext接口调用方法就行了。

 

但是实际上,是需要依据类的多态性来调用以上三个类的具体实现的方法来实现的。

 

 

CoroutineContext源码中定义的 + 操作

public operator fun plus(context: CoroutineContext): CoroutineContext =

    if (context === EmptyCoroutineContext) this else // fast path -- avoid lambda creation

        context.fold(this) { acc, element ->

            val removed = acc.minusKey(element.key)

            if (removed === EmptyCoroutineContext) element else {

                // make sure interceptor is always last in the context (and thus is fast to get when present)

                val interceptor = removed[ContinuationInterceptor]

                if (interceptor == null) CombinedContext(removed, element) else {

                    val left = removed.minusKey(ContinuationInterceptor)

                    if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else

                        CombinedContext(CombinedContext(left, element), interceptor)

                }

            }

        }

 

 

翻译:

 
/**

 * 合并两个上下文,如果有重复的,用右侧的替换,其次,确保拦截器在最后的位置

 * */

public operator fun plus(context: CoroutineContext): CoroutineContext =

        if (context === EmptyCoroutineContext) {

            // fast path -- avoid lambda creation

            //如果要合并的是一个空上下文,直接返回当前的上下文

            this

        } else {

            //如果左右两个上下文都是有内容的

            context.fold(this) { acc, element ->

                //取出右侧的上下文的key,acc.minusKey计算出左侧上下文除去这个key后剩下的上下文内容

                val removed = acc.minusKey(element.key)

                if (removed === EmptyCoroutineContext) {

                    //如果左侧剩下的是空上下文,说明左侧也是只有这一个key对应的值

                    //所以,直接右侧替换左侧,,,返回右侧element即可

                    element

                } else {

                    //如果左侧的上下文有自己的内容,接下来就是要合并两个上下文了,

                    // 同时将相同key值的替换为右侧的

                    // make sure interceptor is always last in the context (and thus is fast to get when present)

                    // 确保拦截器始终位于上下文中的最后一个(因此在出现时可以快速获取)

                    //感觉这里得context 已经开始都是一个CombinedContext类型了吧

                    val interceptor = removed[ContinuationInterceptor]

                    if (interceptor == null) {

                        //如果左侧没有拦截器,直接将左右合并

                        CombinedContext(removed, element)//A

                    } else {

                        //如果左侧有拦截器,则先取出其他的上下文

                        val left = removed.minusKey(ContinuationInterceptor)

                        if (left === EmptyCoroutineContext) {

                            //如果其他的是空上下文,,则说明左侧只剩下一个拦截器,直接将拦截器合并到右侧的

                            CombinedContext(element, interceptor)//B:  注意参数位置,和A处对比

                        } else {

                            //如果左侧除了拦截器,还有其他的上下文,为了确保拦截器在最后,应该将其他的和右侧合并,

                            //最后再合并拦截器到右侧

                            CombinedContext(CombinedContext(left, element), interceptor)

                        }

                    }

                }

            }

        }

也就是说 A + B 就会执行这个CoroutineContext类的plus 方法

 

plus 方法的关键在于fold方法,而fold方法的关键在于CombinedContext类。

 

fold逻辑实现的关键也在于 CombinedContext 类的 fold 和 CoroutineContext 类的 fold 两个方法配合实现。

其实 12行 context.fold(this) { acc, element -> 大部分时候是在调用 CombinedContext 类的 fold 方法,它的内部依次递归调用 CombinedContext 的成员变量 left 的 fold 方法直到最后调用到 CoroutineContext 类的 fold ,即 Element 类的 fold 方法。

 

有点像 view 层级里的 View和 ViewGroup 的关系,联想事件传递过程。

 

CombinedContext类:

internal class CombinedContext(

    private val left: CoroutineContext,

    private val element: Element

) : CoroutineContext, Serializable {


/**

*CombinedContext重写了get方法,可以看出来CombinedContext本身是不作为真正的上下文的。它只是一个包装袋

*/

override fun <E : Element> get(key: Key<E>): E? {

    var cur = this

    while (true) {

        //先判断 element 是不是,这是最快的方式,如果是,直接return

        cur.element[key]?.let { return it }

        //element 不是,则开始判断 left

        val next = cur.left

        if (next is CombinedContext) {

            //如果 left 是一个 CombinedContext 类型,则对 left 进行当前的 get 方法

            //: left 赋值给cur , 进入下一次的while循环

            cur = next

        } else {

            //如果 left 是一个 CoroutineContext 的真正实现类,则对 left 进行 key 判断后返回 left 或者 null

            return next[key]

        }

    }

}

...


public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =

    // 如果 left  也是一个 CombinedContext, 则对 left 继续调用当前的 fold 方法,

    // 如果 left 是一个真正的 CoroutineContext 实现类,则调用其实现类的 fold 方法,也就是Element 类的fold方法,

    // Element 的fold 方法的返回值会作为参数回传给一层层的 operation 调用,直到回到这里的最外面的这个 operation 表达式

    operation(left.fold(initial, operation), element)

可以看到CombinedContext的fold方法执行了 operation(left.fold(initial, operation), element),首先会去调用left的fold方法:left.fold(initial, operation)。

这里会判断left具体是什么类型,如果是CombinedContext,那还是会调用CombinedContext类的fold方法。也就是说,CombinedContext的fold方法会一直取left变量的left变量,,,,直到最后,left是一个Element类型,left.fold(initial, operation)方法就会去执行Element类的fold方法了。

 

Element类:

public interface Element : CoroutineContext {

    /**

     * A key of this coroutine context element.

     */

    public val key: Key<*>


    public override operator fun <E : Element> get(key: Key<E>): E? =

        @Suppress("UNCHECKED_CAST")

        if (this.key == key) this as E else null


    public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =

        operation(initial, this)


    public override fun minusKey(key: Key<*>): CoroutineContext =

        if (this.key == key) EmptyCoroutineContext else this

}

可以看到,Element的fold方法就是单纯的将自己和方法调用者(即最最外层的CoroutineContext对象,也就是一开始调用CombinedContext的fold方法的那个变量,这个变量可能是一个CoroutineContext,也可能是一个CombinedContext,一般应该就是一个CombinedContext)传进了operation代码块里。

 

Element类的fold方法执行完operation(initial, this)会返回一个CoroutineContext对象cc,这个cc是什么呢?

回到plus方法的12行:

 context.fold(this) { acc, element -> ...

 

后面花括号包裹的整个代码块,就是CombinedContext类的fold方法传进去的那个operation 表达式。

 

所以上面说的Element类的fold方法最后返回的cc是什么,就是CoroutineContext类的plus方法中的12行的整个花括号里面的返回值,可以看出,这里要么直接返回一个CoroutineContext类型,要么返回一个CombinedContext类型。operation表达式一层层的返回,就是一遍遍的将一个CoroutineContext作为一个left,然后和一个Element组装成一个CombinedContext,再将这个CombinedContext作为一个left,再和一个Element组装成一个CombinedContext。。。。最后operation表达式返回的CombinedContext就是plus方法返回的对象,也就是 A+ B 得到的结果。

 

我们在协程里面通常看到的上下文 A 的类型直接是接口类型CoroutineContext,但实际上,我们可取可用到的上下文是一个CombinedContext类型。所以会有一种错觉:A就是一个简单的CoroutineContext对象,却可以通过A[key]取到多个不同的上下文对象。A实际如下图:

 

 

可视化协程上下文相加的过程

假设:代码运行过程中, 一共会累加如下 a,b,c,d,interceptor 5个上下文

计算:

a+b = M

c+interceptor = N

d+M = R

N+ R =Res

看最后abcd和interceptor 5个上下文是如何存在的

 

1、a+b = M

 

2、c+interceptor = N

 

3、d+M = R

4、N+ R =Res

第一步,先处理d所在的层级,此时开始考虑interceptor的特殊性了,本来只需要将N和d组合就行,但是interceptor存在了,所以N和d组合变为先取出interceptor,然后c和d组合,然后再和刚才拿出来的interceptor组合。

接下来,还需要组合d-a中剩下的a.

同样,因为interceptor的存在,不能直接组合

到S4的时候,原来的R中d-a部分,也就是left,已经处理完成了,接下来继续将R中剩下的b,即element变量组合进来。

同样,还是要特殊处理interceptor。

最终,生成了的Res就是N+R最后的结果,可以看到,Res中,interceptor虽然早早就参与了运算,但是此时依然是在Res的element位置,即整个上下文链的最外端,从而实现了最快的速度找到interceptor。

为啥放这就是最快的呢,回头看一眼CombinedContext的get方法就知道了。

看废了吗^_^

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值