CoroutineContext 数据结构分析

前提

这篇文章只分析CoroutineContext的数据结构,并不分析为什么要这做。

数据结构

在这里插入图片描述

CoroutineContext 可以看做一个特殊的 二叉树,图(1) 就是它的结构:

它的特殊在于:

  1. 右边只能是非叶节点
  2. 有左节点,一定有右节点
  3. 中间节点只是用来连接左右两个子节点,没有实际的作用。
  4. 每个节点继承 CoroutineContext,看右图(部分继承图)
  5. 每种类型的节点有自己的plus()、minKey()和get()的方法。

这里大概说一下节点的类型,可以看到 图(2)

  • CombinedContext:一定是非叶节点
  • Element:一定不是中间节点

为什么是二叉树呢?

分析plus()的源码知道,每次加入给一个element都是CombinedContext来构建中间节点

CombinedContext 的构造函数:

CombinedContext(
    private val left: CoroutineContext,
    private val element: Element
)

可以发现:left 就是左子树,element就是右子树,element不可能是CombinedContext类型,说明右边只能是叶子节点


Get()

作用:后序遍历RLD,通过key可以来获取Element.

// from 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] // 不是CombinedContext就一定是Element
        }
    }
}
// from EmptyCoroutineContext
public override fun <E : Element> get(key: Key<E>): E? = null
// from Element
public override operator fun <E : Element> get(key: Key<E>): E? = if (this.key === key) this as E else null
  • EmptyCoroutineContext:没有元素,直接为null

  • Element:判断是不是自己,不是就是返回null,是就返回自己

    这里特别要注意,element的key不是唯一的,可能多个element有同一个key,因为这样才能实现 加法,也就是plus方法。

  • CombinedContext:先对比右边的元素是不是,再对比左边的元素是不是。

    CombinedContext(val left: CoroutineContext, val element: Element)
    

    由CombinedContext构造函数可以知道,CombinedContext 右边一定是Element.

示例分析

举个例子:后序遍历RLD,由于非叶节点,没有这里不考虑,所以遍历的顺序就是:3,5,7,6


minusKey()

作用:后序遍历RLD,找到Key相等的elementA,然后删除左子树

// from 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) // 如果是已经处理完的左子树,则重新组合父节点
  }
}

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

// from EmptyCoroutineContext
public override fun minusKey(key: Key<*>): CoroutineContext = this

示例:后序遍历RLD,因为非叶节点是CombinedContext,没有key,所以不会被minus

分析可以知道:

  • 当叶子节点是Element做减法的时候:如果相等那么返回EmptyCoroutineContext,如果不相等则返回本身。
  • 当叶子节点是EmptyCoroutineContext,返回本身。
  • 如果是非叶节点
    • 如果删除的是右叶子节点,返回左子树
    • 如果删除的是左叶子节点
      • 如果没有要删除的,那么返回本身,递归向上
      • 如果左子树被删除了,则返回右叶子节,递归向上
      • 如果是已经处理完的左子树,则重新组合父节点,递归向上

示例分析

第一个示例:
在这里插入图片描述

第二个示例:
在这里插入图片描述

第三个示例:
在这里插入图片描述


fold()

作用:后序遍历LRD,执行operation方法。

这里存在问题,要重新分析。

// from CoroutineContext
public fun <R> fold(initial: R, operation: (R, Element) -> R): R
// from CombinedContext
public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
        operation(left.fold(initial, operation), element)
// from EmptyCoroutineContext
public override fun <R> fold(initial: R, operation: (R, Element) -> R): R = initial
// from Element
public override fun <R> fold(initial: R, operation: (R, Element) -> R): R = operation(initial, this)

分析可以发现:

  • CombinedContext:CombinedContext 会递归拿到 最左边的context进行operation操作,再返回和右边的element进行operation
  • EmptyCoroutineContext:没有左边的值,直接返回要fold的
  • Element:没有左边的值,直接用当前的context 和 要fold的值进行 operation

这里operation 的操作可以往上查找会发现在plus()中用到。

示例分析

举个例子:后序遍历LRD,先对 (6,7) 操作,获取返回值A,再对 (A,5) 操作,返回B,再对(B,3)操作.


plus()

先看一下源码(加了一些花括号)

// from CoroutineContext
public operator fun plus(context: CoroutineContext): CoroutineContext =
    if (context === EmptyCoroutineContext) {
      this 
    } else { // fast path -- avoid lambda creation
        context.fold(this) { acc, element -> // acc 代表左边已经构成的树,element是当前树这一层的 右节点
            val removed = acc.minusKey(element.key) // 将数据分成两棵树且不重复,removed 和 element
            if (removed === EmptyCoroutineContext) { // removed没有数据,直接返回element。
              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) { // 判断removed是具有Interceptor,没有则左子树为removed,右子树为element
                  CombinedContext(removed, element)
            	} else { 
                    val left = removed.minusKey(ContinuationInterceptor) // 将removed根据是否具有Interceptor划分
                    if (left === EmptyCoroutineContext) { // removed 只有一个Interceptor
                      CombinedContext(element, interceptor) 
                    } else { // 拿到前层级的右子树element 与 不具有interceptor的左子树组合
                        CombinedContext(CombinedContext(left, element), interceptor)
                    }
                }
            }
        }
    }

从 加树的最左边的叶子 开始加入到 被加树 中。

Fold 主要做三件事:

  1. 去重
  2. 去除EmptyCoroutineContext
  3. 保证ContinuationInterceptor一定处于第一层右边叶子节点。

示例分析

从左边加和右边加 形成的树不一样:
在这里插入图片描述

如果有Interceptor,那么他一定是处于树的第一层 右边叶子节点.
在这里插入图片描述

两个树相加:
在这里插入图片描述

总结

CoroutineContext :定义加入和减去node的标准,以及获取以自己为根节点的标准

Element:继承CoroutineContext,一定是叶子节点,有自己的get,plus,minus的标准

CombinedContext:继承CoroutineContext,一定是非叶节点,有自己的get,plus,minus的标准

对于整个Context的树来说:

  • get:后序遍历RLD,对比key获取context,找不到返回null
  • minusKey:后序遍历RLD,找到Key相等的elementA,删除对应的element,重新构建树
  • fold:后序遍历LRD,意义在于先构建左子树,在递归向上与右子树结合
  • plus:后序遍历LRD,需要保证ContinuationInterceptor一定处于第一层右边叶子节点。


附录——如何验证文章的正确性?

自定义Element

class Element1 : AbstractCoroutineContextElement(Key) {
    companion object Key : CoroutineContext.Key<Element1>

    override fun toString(): String {
        return this::class.java.name
    }
}

进行加法组合:

val context1 = Element1() +  Element2()

val context2 =  Element3() + Element4()

println(context1 + Element3())
println(context2 + context1)
println(context1 + context3)
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值