CoroutineContext源码分析

context,即协程上下文

public interface Continuation<in T> {

   /**

    * The context of the coroutine that corresponds to this continuation.

    */

   public val context: CoroutineContext

   /**

    * Resumes the execution of the corresponding coroutine passing a successful or failed [result] as the

    * return value of the last suspension point.

    */

   public fun resumeWith(result: Result<T>)

}

基本使用

本文主要分析CoroutineContext实现原理,并不会写太多使用上的内容。

CoroutineContext.Element有一个抽象类AbstractCoroutineContextElement有了它创建一个元素就很简单了,直接提供Key即可。

个人理解这种设计方式类似AbstractLis,利用一个抽象类,对接口的一些公用、高频方法进行一些默认的实现。有特殊需求单独实现。这样继承它用起来比较方便

//Base class for [CoroutineContext.Element] implementations.

abstract class AbstractCoroutineContextElement{

 public override val key: Key<*>

}: Element

创建元素并不难,提供对应的Key即可,我们可以利用它来实现一个协程名

class CoroutineName(val name: String): AbstractCoroutineContextElement(CoroutineName.Key){

 companion object Key: CoroutineContext.Key<CoroutineName>//伴生对象

 override fun toString(): String = "CoroutineName($name)"

}

把定义好的协程上下文赋值给作为完成回调的Continuation(completion)实例,这样就可以将它绑定到协程上了。

fun test(){

 suspend{

   //协程体...

}.startCoroutine(MyContinuation())

}

class MyContinuation() : Continuation<Int> {

   //绑定上下文,这里我故意设置两个name ,我就是玩儿

   override val context: CoroutineContext = CoroutineName("Co-01") + CoroutineName("Co-02")

   override fun resumeWith(result: Result<Int>) {

       context[CoroutineName].toString()//上下文使用

  }

}

在我学习上下文的时候,当我看到context[CoroutineName]CoroutineName("Co-01") + CoroutineName("Co-02")我立即意识到它重载了操作符,立马点开CoroutineContext源码,当场逮捕如下两个方法

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

public operator fun plus(context: CoroutineContext): CoroutineContext

不知道你们想到了吗?如果没想到说明对kotlin语法还不太熟悉哦 ~


现在可以直接拉到文章底部看总结的结论

从这里开始,全是结论的源码证明。

CoroutineContex分析

伪代码结构

public interface CoroutineContext {

//重载get 实现context[key]用法    

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

 

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

    //重载plus 实现 + ,返回值是 CoroutineContext 类型。

    //override val context: CoroutineContext = CoroutineName("Co-01") + CoroutineName("Co-02")

    //这个方法非常关键,下面会详细解读

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

    //返回一个去除链表中key的对象的上下文。

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

    //[CoroutineContext] 元素的键。 [E] 是一种具有此键的元素。E必须是Element的子类型,约等于 E extends Element.

    public interface Key<E : Element>

   

    // Element是上下文的元素接口,这里挺有意思它竟然也实现了CoroutineContext

    public interface Element : CoroutineContext {

       /**

        * 一个抽象属性,让子类必须提供的,就是key么。标识

        */

        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 也是CoroutineContext 子类,但是它也重写了CoroutineContext 的方法,它跟CoroutineContext直接实现类是有明显区别的。

上下文数据结构

CombinedContext

internal class CombinedContext(

   private val left: CoroutineContext,

   private val element: Element

) : CoroutineContext, Serializable {

   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]

          }

      }

  }

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

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

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

       //如果element[key]不等于空,即key相等,说明element被命中了,element需要被抛弃,let是内联函数,所以等效于直接返回left。否则继续向下递归执行

       //题外话:这就说明为啥拦截器放在队尾,因为这样直接就返回了,效率很高省去迭代。当然不是在这里get,但是道理是一样的。顺便提一嘴。

       element[key]?.let { return left }

       val newLeft = left.minusKey(key)//递归到最原始的节点的left就是Element,如果命中相等就是EmptyCoroutineContext。

       //假设我们递归至最原始的left,left命中了。

       return when {

           newLeft === left -> this//如果没命中

           //如果链表尾部命中,返回element即可。

           newLeft === EmptyCoroutineContext -> element

           //如果中间,或者头部命中。把断开的链子接上。

           else -> CombinedContext(newLeft, element)

      }

  }

   private fun size(): Int {

       var cur = this

       var size = 2

       while (true) {

           cur = cur.left as? CombinedContext ?: return size

           size++

      }

  }

   private fun contains(element: Element): Boolean =

       get(element.key) == element

   private fun containsAll(context: CombinedContext): Boolean {

       var cur = context

       while (true) {

           if (!contains(cur.element)) return false

           val next = cur.left

           if (next is CombinedContext) {

               cur = next

          } else {

               return contains(next as Element)

          }

      }

  }

   override fun equals(other: Any?): Boolean =

       this === other || other is CombinedContext && other.size() == size() && other.containsAll(this)

   override fun hashCode(): Int = left.hashCode() + element.hashCode()

   override fun toString(): String =

       "[" + fold("") { acc, element ->

           if (acc.isEmpty()) element.toString() else "$acc, $element"

      } + "]"

}

CombinedContext是CoroutineContext的实现类

实现 CoroutineContext接口代表列表,实现CoroutineContext.Element的是添加到列表里的元素

CombinedContext 是一个递归列表,跟我们经常用的List Node节点还有点不太一样。left指向上一个节点,但他们是嵌套关系,element代表当前元素

通过这幅图,要试着理解这种”嵌套“或者说”包裹“的关系。

实例2可以理解为之前原有的上下文对象。My5CoroutineName可以理解需要新插入列表中的Element,实例2.plusMy5CoroutineName后,创建并返回了实例1,新增节点把老节点包起来存在left。跟双链表那种插入元素修改指针指向的方式有明显区别。

EmptyCoroutineContext

还有这个数据结构,空上下文。

public object EmptyCoroutineContext : CoroutineContext, Serializable {

   private const val serialVersionUID: Long = 0

   private fun readResolve(): Any = EmptyCoroutineContext

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

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

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

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

   public override fun hashCode(): Int = 0

   public override fun toString(): String = "EmptyCoroutineContext"

}

完整代码分析

开始之前,再来重温一下这段代码

class MyContinuation() : Continuation<Int> {

   override val context: CoroutineContext = CoroutineName("Co-01") + CoroutineName("Co-02")

   override fun resumeWith(result: Result<Int>) {

       context[CoroutineName].toString()//上下文使用

  }

}

首先肯定是不能不初始化context,所以要么是EmptyCoroutineContext,要么是Element。

其次CoroutineName 是 Element类型,CombinedContext是私有类。我们不能构造一个CombinedContext赋值给context。

临时结论:

上下文列表的第一个CombinedContext的left 一定是 Element

之所以定这个结论,是因为后面涉及到很多递归代码,所以必须把结束递归的条件搞清楚。

完整代码

我们从上面的例程出发,来探索一下CoroutineContext的数据结构,此次分析代号K1.

案例K1

public interface CoroutineContext {

   

//有三处实现,分别是:Element、CombinedContext、EmptyCoroutineContext

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

   //有三处实现,分别是:Element、CombinedContext、EmptyCoroutineContext

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

   //只有这一处实现

   //注意:假设CoroutineName("Co-01") + CoroutineName("Co-02") 缩写为: co1.plus(co2)那么下面的this指的是co1 context指的是co2

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

        //传入的元素是空那么没必要拼接直接返回当前(this)

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

           //1,这里开始代码很杂乱,不要急,一步一步得捋。肯定能轻松看明白

           //2.首先花2分钟去上面仔细看fold函数的定义,如果看不懂,把 CoroutineContext 当成R类型带入,你就明白下面传this的目的了。

  //3.这里简单分析一下第一个参数要求是 CoroutineContext 的对象,这里传了[this]就是当前的上下文对象

  //第二个参数是一个函数,所以根据Lambda简化规则,可以直接跟着{...}.两个参数分别是 acc: CoroutineContext,element: Element

  //4.想必你也猜出来了,我干脆再强势一点,直接告诉你acc代表[left],element 对应 [element]

   //5.注意,这里调用flod的目的仅仅是定义了 Lambda 表达式,但是此处并没有调用这个表达式。我当时也被这个高阶函数搞懵一小下下。

   

  //已知co1是Element类型,看下Element::fold的实现,豁然开朗了,继续

 

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

               //acc是co1 ,element元素是[传入的context] co2

//看看Element的minusKey,key相等,返回了EmptyCoroutineContext,这里就意味着把co1给删了,因为他们相等啊。

               val removed = acc.minusKey(element.key)

               //EmptyCoroutineContext 返回时表示key相同,直接把新element返回了。即现在的context 就等于 CoroutineName("Co-02")。

               //【K1】流程分析到此结束。继续拐回去,分析一下如果继续追加上下文,CoroutineName("Co-02") + DispatcherContext。会发生什么事情。

               if (removed === EmptyCoroutineContext) element else {

                   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)

                  }

              }

          }

   /**

    *返回一个去除链表中key的对象的上下文。

    */

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

   /**

    * Key 标识接口

    */

   public interface Key<E : Element>

   /**

    * 上下文链表中元素标识接口

    */

   public interface Element : CoroutineContext {

       /**

        * 这个元素对应的键

        */

       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

       /**

        * 进行累加的高阶函数。由于element对象不是一个集合对象,所以这里不会递归,只会执行一次 operation

        */

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

           operation(initial, this)

       /**

        * 如果当前key相等那么返回EmptyCoroutineContext。这个函数寓意就是用来去除key

        */

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

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

  }

}

在分析一个情况:CoroutineName("Co-02") + DispatcherContext

此次分析代号K2.

案例K2

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

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

         

  //已知this为co2,是Element类型

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

               //acc是co2 ,element元素是[传入的拦截器]缩写为:DP

//看看Element的minusKey,key不相等,removed=co2

               val removed = acc.minusKey(element.key)

               if (removed === EmptyCoroutineContext) element else {//进到这里。

                   //已知removed代表老列表,即co2.

                   //这里做的事情目的:由于拦截器开发中使用频繁,所以这里是做优化,把拦截器放在链表末尾,增加获取拦截器时的效率

                   //查看 Element::get 可知key不相同,remove是Name而element是ContinuationInterceptor,这里返回null

                   val interceptor = removed[ContinuationInterceptor]

                   //如果拦截器对象为空的话,直接把Dp(拦截器)拼接CombinedContext对象中。这时候天然在队尾。【K2】分析完成。

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

                       val left = removed.minusKey(ContinuationInterceptor)

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

                           CombinedContext(CombinedContext(left, element), interceptor)

                  }

              }

          }

继续:

CoroutineName("Co-02") + DispatcherContext + ExceptionHandlerContext

此次分析代号K3.

案例K3

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

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

         

  //已知this为CombinedContext(left:co2,element:disp),context 为 ExceptionHandlerContext,Elemnet类型

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

               //acc是CombinedContext(co2,disp) ,element元素是[传入的context] Ex

//看看CombinedContext的minusKey,key不相等,removed=co2

               val removed = acc.minusKey(element.key)//可以去看CombinedContext::minusKey,做了简单注释

               if (removed === EmptyCoroutineContext) element else {//没有命中,进到这里。

                   //已知removed代表老列表,即CombinedContext(left:co2,element:disp).

                   //这里做的事情目的:由于拦截器开发中使用频繁,所以这里是做优化,把拦截器放在链表末尾,增加获取拦截器时的效率

                   //查看CombinedContext::get ,找到拦截器dp,

                   val interceptor = removed[ContinuationInterceptor]

                   //如果没有拦截器,直接加到队尾就行了。因为这样不影响拦截器在队尾的原则

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

//我们之前设置dp,所以不为空。查看CombinedContext::minusKey,命中了dp,把除了dp其余的部分当作left返回了。

                       val left = removed.minusKey(ContinuationInterceptor)

                       //如果新left为空,那么就没必要保留他了。直接让它消失。

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

                           //拦截器放链表最后

                           CombinedContext(CombinedContext(left, element), interceptor)

                  }

              }

          }

不知道你们感受如何,反正我是要写到吐了。递归代码就是这样不易阅读。只能通过逐句解析,让自己尽量保持熟悉程度。

再来一个最色的分析-----K4,当作本篇的一个终结

案例K4

val context1:CoroutineContext = CoroutineName("Co-01");

val context2:CoroutineContext = DispatcherContext + ExceptionHandlerContext;

....

override val context: CoroutineContext = context1 + context2;

这样之前的的分析逻辑就全变了。context1 是 element,context2是CombinedContext

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

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

         

  //已知this为CoroutineName(Element),context 为 CombinedContext(left: dp, element: Ex)

           context.fold(this) { acc, element -> //fold 不再是 Element::fold 而是 CombinedContext::fold。

               //acc是left.fold(initial, operation),又是递归,fk

  /*即:dp.fold(acc:CoroutineName,operation)。

                               这又回到了 Element::fold ==> dp.fold 中 acc:CoroutineName,element this即dp                   */

               //acc:CoroutineName不包括dp,所以remove 等于 acc:CoroutineName                  

               val removed = acc.minusKey(element.key)

               if (removed === EmptyCoroutineContext) element else {//没有命中,进到这里。

                   //已知removed代表老列表,即acc:CoroutineName

                   val interceptor = removed[ContinuationInterceptor]

                   //如果没有拦截器,直接加到队尾CombinedContext(CoroutineName, dp) dp.fold 返回了CombinedContext(CoroutineName, dp)

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

                       val left = removed.minusKey(ContinuationInterceptor)

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

                           //拦截器放链表最后

                           CombinedContext(CombinedContext(left, element), interceptor)

                  }

              }

          }

//=============================CombinedContext::fold===========================================

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

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

//=============================Element::fold========================

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

           operation(initial, this)

继续往下:

//=============================CombinedContext::fold===========================================

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

       operation(CombinedContext(CoroutineName, dp), element)//element是Ex。

//回到上层递归

{ acc, element ->

               //acc是CombinedContext(CoroutineName, dp),element:ex

               //acc不包括 ex ,所以 remove 等于 CombinedContext(CoroutineName, dp)

               val removed = acc.minusKey(element.key)

               if (removed === EmptyCoroutineContext) element else {//进到这里。

                   //已知removed代表老列表,即CombinedContext(CoroutineName, dp)

                   val interceptor = removed[ContinuationInterceptor]//取出dp拦截器这一项

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

                       //进到这里

                       val left = removed.minusKey(ContinuationInterceptor)//CoroutineName

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

                      //最后的返回值。left :CoroutineName,element : ex , interceptor: dp.【K4】分析结束

                           CombinedContext(CombinedContext(left, element), interceptor)

                  }

              }

结论

1.CoroutineName("Co-02") + EmptyCoroutineContext 结果:CoroutineName("Co-02")

2.CoroutineName("Co-01") + CoroutineName("Co-02") = CoroutineName("Co-02")自动去重,新的替换老的

3.CoroutineName("Co-01") + Dispatchers.Main + ExceptionHandlerCoroutineContext = CoroutineName("Co-01") <- ExceptionHandlerCoroutineContext <- Dispatchers.Main (拦截器ContinuationInterceptor一定在链表尾部(最右侧),这样的好处是get直接拿不用向左遍历)

当context+element的时候

  • 如果context等于empty,那么直接返回element
  • 如果context含有与element的key相同的元素,那么context删除老元素,添加新元素。
  • 每添加一个新元素后,会检测链表中是否有“拦截器”如果有,则必须把拦截器放在队尾,降低get成本
  • 当element为Element的类型时

    • context + CoroutineName = CombinedContext(left: context,element: CoroutineName );
  • 当element为CombinedContext时

    • (context + CombinedContext(A,B,C)= CombinedContext(left:CombinedContext(left:CombinedContext(left: context,element: A ),element: B),element: C);
    • 上面的式子只是想说明,[addAll]操作并不是简单的通过更改链表指针的方式实现,取出目标列表中每一个element,创建新的CombinedContext键入到原来的上下文表中。从头到尾通过递归,全部都是新创建的节点。

以上是 CoroutineContext源码分析 的全部内容, 来源链接: utcz.com/z/267663.html

回到顶部