• Kotlin协程:协程上下文与上下文元素


    一.EmptyCoroutineContext

        EmptyCoroutineContext代表空上下文,由于自身为空,因此get方法的返回值是空的,fold方法直接返回传入的初始值,plus方法也是直接返回传入的context,minusKey方法返回自身,代码如下:

    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"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    二.CombinedContext

        CombinedContext是组合上下文,是存储Element的重要的数据结构。内部存储的组织结构如下图所示:
    在这里插入图片描述
        可以看出CombinedContext是一种左偏(从左向右计算)的列表,这么设计的目的是为了让CoroutineContext中的plus方法工作起来更加自然。

        由于采用这种数据结构,CombinedContext类中的很多方法都是通过循环实现的,代码如下:

    internal class CombinedContext(
        // 数据结构左边可能为一个Element对象或者还是一个CombinedContext对象
        private val left: CoroutineContext,
        // 数据结构右边只能为一个Element对象
        private val element: Element
    ) : CoroutineContext, Serializable {
    
        override fun <E : Element> get(key: Key<E>): E? {
            var cur = this
            while (true) {
                // 进行get操作,如果当前CombinedContext对象中存在,则返回
                cur.element[key]?.let { return it }
                // 获取左边的上下文对象
                val next = cur.left
                // 如果是CombinedContext对象
                if (next is CombinedContext) {
                    // 赋值,继续循环
                    cur = next
                } else { // 如果不是CombinedContext对象
                    // 进行get操作,返回
                    return next[key]
                }
            }
        }
        // 数据结构左右分开操作,从左到右进行fold运算
        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对象,则返回左边
            element[key]?.let { return left }
            // 调用左边的minusKey方法
            val newLeft = left.minusKey(key)
            return when {
                // 这种情况,说明左边部分已经是去掉指定的Element对象的,右边也是如此,因此返回当前对象,不需要在进行包裹
                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++
            }
        }
    
        // 通过get方法实现
        private fun contains(element: Element): Boolean =
            get(element.key) == element
    
        private fun containsAll(context: CombinedContext): Boolean {
            var cur = context
            // 循环展开每一个CombinedContext对象,每个CombinedContext对象中的Element对象都要包含
            while (true) {
                if (!contains(cur.element)) return false
                val next = cur.left
                if (next is CombinedContext) {
                    cur = next
                } else {
                    return contains(next as Element)
                }
            }
        }
        ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

    三.Key与Element

        Key接口与Element接口定义在CoroutineContext接口中,代码如下:

    public interface Key<E : Element>
    
    public interface Element : CoroutineContext {
        // 一个Key对应着一个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
        // 自身与初始值进行fold操作
        public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
            operation(initial, this)
        // 如果要去除的是当前的Element对象,则返回空的上下文,否则返回自身
        public override fun minusKey(key: Key<*>): CoroutineContext =
            if (this.key == key) EmptyCoroutineContext else this
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    四.CoroutineContext

        CoroutineContext接口定义了协程上下文的基本行为以及Key和Element接口。同时,重载了"+"操作,相关代码如下:

    public interface CoroutineContext {
       
        public operator fun <E : Element> get(key: Key<E>): E?
    
        public fun <R> fold(initial: R, operation: (R, Element) -> R): R
    
        public operator fun plus(context: CoroutineContext): CoroutineContext =
            // 如果要与空上下文相加,则直接但会当前对象,
            if (context === EmptyCoroutineContext) this else
                // 当前Element作为初始值
                context.fold(this) { acc, element ->
                    // acc:已经加完的CoroutineContext对象
                    // element:当前要加的CoroutineContext对象
                    
                    // 获取从acc中去掉element后的上下文removed,这步是为了确保添加重复的Element时,移动到最右侧
                    val removed = acc.minusKey(element.key)
                    // 去除掉element后为空上下文(说明acc中只有一个Element对象),则返回element
                    if (removed === EmptyCoroutineContext) element else {
                        // ContinuationInterceptor代表拦截器,也是一个Element对象
                        // 下面的操作是为了把拦截器移动到上下文的最右端,为了方便快速获取
                        // 从removed中获取拦截器
                        val interceptor = removed[ContinuationInterceptor]
                        // 若上下文中没有拦截器,则进行累加(包裹成CombinedContext对象),返回
                        if (interceptor == null) CombinedContext(removed, element) else {
                            // 若上下文中有拦截器
                            // 获取上下文中移除到掉拦截器后的上下文left
                            val left = removed.minusKey(ContinuationInterceptor)
                            // 若移除到掉拦截器后的上下文为空上下文,说明上下文left中只有一个拦截器,
                            // 则进行累加(包裹成CombinedContext对象),返回
                            if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else
                                // 否则,现对当前要加的element和left进行累加,然后在和拦截器进行累加
                                CombinedContext(CombinedContext(left, element), interceptor)
                        }
                    }
                }
    
        public fun minusKey(key: Key<*>): CoroutineContext
        
        ... // (Key和Element接口)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    1.plus方法图解

        假设我们有一个上下文顺序为A、B、C,现在要按顺序加上D、C、A。

    1)初始值A、B、C
    在这里插入图片描述
    2)加上D
    在这里插入图片描述
    3)加上C
    在这里插入图片描述
    4)加上A
    在这里插入图片描述

    2.为什么要将ContinuationInterceptor放到协程上下文的最右端?

        在协程中有大量的场景需要获取ContinuationInterceptor。根据之前分析的CombinedContext的minusKey方法,ContinuationInterceptor放在上下文的最右端,可以直接获取,不需要经过多次的循环。

    五.AbstractCoroutineContextKey与AbstractCoroutineContextElement

        AbstractCoroutineContextElement实现了Element接口,将Key对象作为构造方法必要的参数。

    public abstract class AbstractCoroutineContextElement(public override val key: Key<*>) : Element
    
    • 1

        AbstractCoroutineContextKey用于实现Element的多态。什么是Element的多态呢?假设类A实现了Element接口,Key为A。类B继承自类A,Key为B。这时将类B的对象添加到上下文中,通过指定不同的Key(A或B),可以得到不同类型对象。具体代码如下:

    // baseKey为衍生类的基类的Key
    // safeCast用于对基类进行转换
    // B为基类,E为衍生类
    public abstract class AbstractCoroutineContextKey<B : Element, E : B>(
        baseKey: Key<B>,
        private val safeCast: (element: Element) -> E?
    ) : Key<E> {
        // 顶置Key,如果baseKey是AbstractCoroutineContextKey,则获取baseKey的顶置Key
        private val topmostKey: Key<*> = if (baseKey is AbstractCoroutineContextKey<*, *>) baseKey.topmostKey else baseKey
    
        // 用于类型转换
        internal fun tryCast(element: Element): E? = safeCast(element)
        // 用于判断当前key是否是指定key的子key
        // 逻辑为与当前key相同,或者与当前key的顶置key相同
        internal fun isSubKey(key: Key<*>): Boolean = key === this || topmostKey === key
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    1.getPolymorphicElement方法与minusPolymorphicKey方法

        如果衍生类使用了AbstractCoroutineContextKey,那么基类在实现Element接口中的get方法时,就需要通过getPolymorphicElement方法,实现minusKey方法时,就需要通过minusPolymorphicKey方法,代码如下:

    public fun <E : Element> Element.getPolymorphicElement(key: Key<E>): E? {
        // 如果key是AbstractCoroutineContextKey
        if (key is AbstractCoroutineContextKey<*, *>) {
            // 如果key是当前key的子key,则基类强制转换成衍生类,并返回
            @Suppress("UNCHECKED_CAST")
            return if (key.isSubKey(this.key)) key.tryCast(this) as? E else null
        }
        // 如果key不是AbstractCoroutineContextKey
        // 如果key相等,则强制转换,并返回
        @Suppress("UNCHECKED_CAST")
        return if (this.key === key) this as E else null
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    public fun Element.minusPolymorphicKey(key: Key<*>): CoroutineContext {
        // 如果key是AbstractCoroutineContextKey
        if (key is AbstractCoroutineContextKey<*, *>) {
            // 如果key是当前key的子key,基类强制转换后不为空,说明当前Element需要去掉,因此返回空上下文,否则返回自身
            return if (key.isSubKey(this.key) && key.tryCast(this) != null) EmptyCoroutineContext else this
        }
        // 如果key不是AbstractCoroutineContextKey
        // 如果key相等,说明当前Element需要去掉,因此返回空上下文,否则返回自身
        return if (this.key === key) EmptyCoroutineContext else this
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
  • 相关阅读:
    C练题笔记之:Leetcode-654. 最大二叉树
    vue学习-12路由组件的基本使用
    深度学习(PyTorch)——循环神经网络(RNN)基础篇五
    网站风格变黑白的方法,用css或javascript方法将网站改为灰色
    数据同步、
    功利点没啥!
    【路径规划】辅助点与多段贝塞尔平滑RRT
    【无标题】
    算法与数据结构(第二周)——排序基础:插入排序法
    计算机毕业设计之java+ssm学术成果管理系统
  • 原文地址:https://blog.csdn.net/LeeDuoZuiShuai/article/details/126077688