CoroutineContext的plus原理

CoroutineContext的plus operation

CoroutineContext中plus()方法会用到三个方法,这三个方法以虚函数的形式定义在CoroutineContext中:

	/**
     * Returns the element with the given [key] from this context or `null`.
     */
    public operator fun <E : Element> get(key: Key<E>): E?

    /**
     * Accumulates entries of this context starting with [initial] value and applying [operation]
     * from left to right to current accumulator value and each element of this context.
     */
    public fun <R> fold(initial: R, operation: (R, Element) -> R): R
    /**
     * Returns a context containing elements from this context, but without an element with
     * the specified [key].
     */
    public fun minusKey(key: Key<*>): 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)
                    }
                }
            }

这三个方法都是在子类中实现的,其第一代子类如下图所示:
在这里插入图片描述

fold展开操作

在plus()方法中先判断被加对象是否是EmptyCoroutineContext,若是则返回自己做为加法的结果,若不是则调用fold()函数进一步处理。这时被加的context对象类型可能是CombinedContext也可能是Element,先看在Element中的实现:

public interface Element : CoroutineContext {
		public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
            operation(initial, this)
}

可以看到直接调用了operation Lambda表达式,即为context.fold(this) { acc, element ->},则acc为加数,element为被加数。再看一下在CombinedContext中的实现:

internal class CombinedContext(
    private val left: CoroutineContext,
    private val element: Element
) : CoroutineContext, Serializable {
	public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
        operation(left.fold(initial, operation), element)
}

CombinedContext类中包含两个成员left和element,left的实际类型可能是CombinedContext或Element,其包含关系如下图所示。参考Kotlin协程上下文CoroutineContext是如何可相加的
在这里插入图片描述
由上面代码和图可知CombinedContext的fold()函数会递归调用left的fold()方法,每一次递归都会执行一次operation操作,知道left的类型是Element为止。

minusKey减法操作

回到CoroutineContext的plus()方法,下一步会执行acc.minusKey(element.key),acc的类型也有两种情况。先看acc为Element时:

public interface Element : CoroutineContext {
	public override fun minusKey(key: Key<*>): CoroutineContext =
            if (this.key == key) EmptyCoroutineContext else this
}

这里相当于是做减法,当被减数Element中的key等于减数的key时,返回EmptyCoroutineContext,当不等时,则返回该Element对象表示不包含减数。
若acc类型的CombinedContext时:

//internal class CombinedContext
public override fun minusKey(key: Key<*>): CoroutineContext {
        element[key]?.let { return left }
        val newLeft = left.minusKey(key)
        return when {
            newLeft === left -> this
            newLeft === EmptyCoroutineContext -> element
            else -> CombinedContext(newLeft, element)
        }
    }

首先判断element是否是key对应的对象,若是则element[key]返回该对象,减去该对象后就将剩下的left返回。若不是则递归调用left的minusKey()方法继续做减法,newLeft则为做完减法的结果对象,若newLeft === left表明left不包含该key什么都没有减去,则返回该this对象,若newLeft === EmptyCoroutineContext表明left即是该key所对应对象,减去left后即应返回element,其余的话将newLeft和element组合成一个CombinedContext对象返回。

get获取操作

再回到CoroutineContext中plus()方法,removed则是做完减法后的结果,若removed === EmptyCoroutineContext表明减数和被减数相等,因此相加的结果是减数element,若不想等则继续调用removed[ContinuationInterceptor],对应Element和CombinedContext中的get()方法。先看Element中的get()方法:

//public interface Element
public override operator fun <E : Element> get(key: Key<E>): E? =
            @Suppress("UNCHECKED_CAST")
            if (this.key == key) this as E else null

很简单即判断传入的key和Element的key是否相等,从而决定是否返回this。再看一下CombinedContext中的get()方法:

//internal class CombinedContext
override fun <E : Element> get(key: Key<E>): E? {
        var cur = this
        while (true) {
            cur.element[key]?.let { return it }
            val next = cur.left
            if (next is CombinedContext) {
                cur = next
            } else {
                return next[key]
            }
        }
    }

在while循环中,先是判断element是否是key对应的对象,若是则返回elememt对象。若不是则判断left是否是CombinedContext类型,若是则令cur = next进入下一次循环,这里实际是递归的While形式。若left不是CombinedContext类型,则调用其get()方法,参考Element的get()方法。
回到CoroutineContext中plus()方法,removed[ContinuationInterceptor]是检查removed中是否包含ContinuationInterceptor对象,若不包含则通过CombinedContext(removed, element)将removed和element组合成一个CombinedContext对象返回。若包含则将该ContinuationInterceptor对象从removed中移除产生新的left,然后再将该ContinuationInterceptor对象放在最外层element的位置,组合成一个CombinedContext对象返回。这样做是通过plus操作保持ContinuationInterceptor在最外层element的位置,当获取ContinuationInterceptor时十分快捷,无需递归查找。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值