Kotlin协程上下文与上下文元素深入理解

目录
  • 一.EmptyCoroutineContext
  • 二.CombinedContext
  • 三.Key与Element
  • 四.CoroutineContext
  • 五.AbstractCoroutineContextKey与AbstractCoroutineContextElement

一.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"
}

二.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)
            }
        }
    }
    ...
}

三.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
}

四.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.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

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
}

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
}
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
}

到此这篇关于Kotlin协程上下文与上下文元素深入理解的文章就介绍到这了,更多相关Kotlin上下文内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Kotlin如何捕获上下文中的变量与常量详解

    Lambda表达式或匿名函数可以访问或修改其所在上下文中的变量和常量,这个过程被称为捕获. fun main(args: Array<String>) { //定义一个函数,该函数的返回值类型为()->List<String> fun makeList(ele: String): () -> List<String> { //创建一个不包含任何元素的List var list: MutableList<String> = mutableListO

  • 一篇文章揭开Kotlin协程的神秘面纱

    前言 Kotlin协程提供了一种新的异步执行方式,但直接查看库函数可能会有点混乱,本文中尝试揭开协程的神秘面纱. 理论 它是什么 这是别人翻译: 协程把异步编程放入库中来简化这类操作.程序逻辑在协程中顺序表述,而底层的库会将其转换为异步操作.库会将相关的用户代码打包成回调,订阅相关事件,调度其执行到不同的线程(甚至不同的机器),而代码依然想顺序执行那么简单. 我的理解:子任务程协作运行,优雅的处理异步问题解决方案. 它能干什么? 我在做安卓开发,它能替换掉Handler,AsyncTask 甚至

  • 使用kotlin协程提高app性能(译)

    协程是一种并发设计模式,您可以在Android上使用它来简化异步执行的代码.Kotlin1.3版本添加了 Coroutines,并基于其他语言的既定概念. 在Android上,协程有助于解决两个主要问题: 管理长时间运行的任务,否则可能会阻止主线程并导致应用冻结. 提供主安全性,或从主线程安全地调用网络或磁盘操作. 本主题描述了如何使用Kotlin协程解决这些问题,使您能够编写更清晰,更简洁的应用程序代码. 管理长时间运行的任务 在Android上,每个应用程序都有一个主线程来处理用户界面并管理

  • 简单介绍Python的Tornado框架中的协程异步实现原理

    Tornado 4.0 已经发布了很长一段时间了, 新版本广泛的应用了协程(Future)特性. 我们目前已经将 Tornado 升级到最新版本, 而且也大量的使用协程特性. 很长时间没有更新博客, 今天就简单介绍下 Tornado 协程实现原理, Tornado 的协程是基于 Python 的生成器实现的, 所以首先来回顾下生成器. 生成器 Python 的生成器可以保存执行状态 并在下次调用的时候恢复, 通过在函数体内使用 yield 关键字 来创建一个生成器, 通过内置函数 next 或生

  • 利用Kotlin的协程实现简单的异步加载详解

    前言 众所周知在android中当执行程序的耗时超过5秒时就会引发ANR而导致程序崩溃.由于UI的更新操作是在UI主线程进行的,理想状态下每秒展示60帧时人眼感受不到卡顿,1000ms/60帧,即每帧绘制时间不应超过16.67ms.如果某项操作的耗时超过这一数值就会导致UI卡顿.因此在实际的开发中我通常把耗时操作放在一个新的线程中(比如从网络获取数据,从SD卡读取图片等操作),但是呢在android中UI的更新只能在UI主线程中进行更新,因此当我们在非UI线程中执行某些操作的时候想要更新UI就需

  • Kotlin学习教程之协程Coroutine

    定义 Coroutine翻译为协程,Google翻译为协同程序,一般也称为轻量级线程,但需要注意的是线程是操作系统里的定义概念,而协程是程序语言实现的一套异步处理的方法. 在Kotlin文档中,Coroutine定义为一个可被挂起的计算实例,下面话不多说了,来一起看看详细的介绍吧. 配置 build.gradle中dependencies 添加下面2行,注意coroutine目前仍处于experiment阶段,但Kotline官方保证向前兼容. dependencies { implementa

  • python线程、进程和协程详解

    引言 解释器环境:python3.5.1 我们都知道python网络编程的两大必学模块socket和socketserver,其中的socketserver是一个支持IO多路复用和多线程.多进程的模块.一般我们在socketserver服务端代码中都会写这么一句: server = socketserver.ThreadingTCPServer(settings.IP_PORT, MyServer) ThreadingTCPServer这个类是一个支持多线程和TCP协议的socketserver

  • 深入浅析python中的多进程、多线程、协程

    进程与线程的历史 我们都知道计算机是由硬件和软件组成的.硬件中的CPU是计算机的核心,它承担计算机的所有任务. 操作系统是运行在硬件之上的软件,是计算机的管理者,它负责资源的管理和分配.任务的调度. 程序是运行在系统上的具有某种功能的软件,比如说浏览器,音乐播放器等. 每次执行程序的时候,都会完成一定的功能,比如说浏览器帮我们打开网页,为了保证其独立性,就需要一个专门的管理和控制执行程序的数据结构--进程控制块. 进程就是一个程序在一个数据集上的一次动态执行过程. 进程一般由程序.数据集.进程控

  • 详解python之协程gevent模块

    Gevent官网文档地址:http://www.gevent.org/contents.html 进程.线程.协程区分 我们通常所说的协程Coroutine其实是corporate routine的缩写,直接翻译为协同的例程,一般我们都简称为协程. 在linux系统中,线程就是轻量级的进程,而我们通常也把协程称为轻量级的线程即微线程. 进程和协程 下面对比一下进程和协程的相同点和不同点: 相同点: 相同点存在于,当我们挂起一个执行流的时,我们要保存的东西: 栈, 其实在你切换前你的局部变量,以及

  • Python并发编程协程(Coroutine)之Gevent详解

    Gevent官网文档地址:http://www.gevent.org/contents.html 基本概念 我们通常所说的协程Coroutine其实是corporateroutine的缩写,直接翻译为协同的例程,一般我们都简称为协程. 在linux系统中,线程就是轻量级的进程,而我们通常也把协程称为轻量级的线程即微线程. 进程和协程 下面对比一下进程和协程的相同点和不同点: 相同点: 我们都可以把他们看做是一种执行流,执行流可以挂起,并且后面可以在你挂起的地方恢复执行,这实际上都可以看做是con

  • Python中协程用法代码详解

    本文研究的主要是python中协程的相关问题,具体介绍如下. Num01–>协程的定义 协程,又称微线程,纤程.英文名Coroutine. 首先我们得知道协程是啥?协程其实可以认为是比线程更小的执行单元. 为啥说他是一个执行单元,因为他自带CPU上下文.这样只要在合适的时机, 我们可以把一个协程 切换到另一个协程. 只要这个过程中保存或恢复 CPU上下文那么程序还是可以运行的. Num02–>协程和线程的差异 那么这个过程看起来和线程差不多.其实不然, 线程切换从系统层面远不止保存和恢复 CP

随机推荐

其他