iis服务器助手广告广告
返回顶部
首页 > 资讯 > 移动开发 >Kotlin协程上下文与上下文元素深入理解
  • 260
分享到

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

Kotlin协程上下文Kotlin上下文 2022-11-13 13:11:58 260人浏览 薄情痞子
摘要

目录一.EmptyCoroutineContext二.CombinedContext三.Key与Element四.CoroutineContext五.AbstractCoroutin

一.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上下文内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: Kotlin协程上下文与上下文元素深入理解

本文链接: https://www.lsjlt.com/news/171366.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Kotlin协程上下文与上下文元素深入理解
    目录一.EmptyCoroutineContext二.CombinedContext三.Key与Element四.CoroutineContext五.AbstractCoroutin...
    99+
    2022-11-13
    Kotlin 协程上下文 Kotlin 上下文
  • kotlin协程上下文异常处理详解
    目录引言一、协程上下文1.CoroutineContext2.CorountineScope3.子协程继承父协程二、协程的异常传递1.协程的异常传播2.不同上下文(没有继承关系)之间...
    99+
    2024-04-02
  • Python上下文管理器深入讲解
    目录引子概念上下文管理协议(Context Management Protocol)上下文管理器(Context Manager)引子 上下文管理器是一种简化代码的有力方式,其内部也...
    99+
    2022-12-21
    Python上下文管理器 Python上下文
  • Python深入02 上下文管理器
    上下文管理器(context manager)是Python2.5开始支持的一种语法,用于规定某个对象的使用范围。一旦进入或者离开该使用范围,会有特殊操作被调用 (比如为对象分配或者释放内存)。它的语法形式是with...as...关闭文件...
    99+
    2023-06-02
  • 深入理解函数执行上下文this
    目录JavaScript 中的 this 是什么全局执行上下文中的 this函数执行上下文中的 this1. 通过函数的 call 方法设置2. 通过对象调用方法设置3. 通过构造函...
    99+
    2022-11-13
    函数执行上下文 this 执行上下文 this
  • 将主协程上下文的副本传递给子例程上下文
    在PHP中,协程是一种强大的编程工具,可以提高代码的执行效率。而在协程中,将主协程上下文的副本传递给子例程上下文是一种常用的操作。通过这种方式,可以在子例程中访问主协程的上下文数据,实...
    99+
    2024-02-10
  • Python协程中使用上下文
    在Python 3.7中,asyncio 协程加入了对上下文的支持。使用上下文就可以在一些场景下隐式地传递变量,比如数据库连接session等,而不需要在所有方法调用显示地传递这些变量。使用得当的话,可以提高接口的可读性和扩展性。 协和...
    99+
    2023-01-30
    上下文 Python 协程中
  • 深人了解Python上下文管理器
    目录with语句上下文管理器创建基于类的上下文管理器@contextmanager 装饰器总结下面先来介绍一下with关键字在文件读写中的应用,简单了解上下文管理器的功能。 with...
    99+
    2024-04-02
  • 深入学习JavaScript执行上下文
    目录前言初始化全局对象(GO)执行上下文全局执行上下文Java Script遇到函数代码如何执行?环境变量和记录总结:前言 我们先不看这个标题,来看下面这段代码是怎么运行的: var...
    99+
    2022-11-13
    JavaScript执行上下文 JavaScript上下文
  • CSS怎么根据上下文选择元素
    这篇文章主要讲解了“CSS怎么根据上下文选择元素”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“CSS怎么根据上下文选择元素”吧! 根据上下文选择元素 我...
    99+
    2024-04-02
  • CSS根据上下文选择元素的方法
    这篇文章主要介绍了CSS根据上下文选择元素的方法的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇CSS根据上下文选择元素的方法文章都会有所收获,下面我们一起来看看吧。 依照高低...
    99+
    2024-04-02
  • SpringMVC深入讲解文件的上传下载实现
    目录SpringMVC文件下载SpringMVC文件上传1.基本介绍2.需求分析/图解3.应用实例4.Debug-file.transferTo(目标文件)SpringMVC文件下载...
    99+
    2024-04-02
  • python上下文管理器协议的实现
    目录前言todo:版本1todo:版本2前言 在上下文管理器协议的过程中,涉及到两个魔术方法__enter__方法 和 __exit__方法 在python中所有实现了上下文管理器协...
    99+
    2024-04-02
  • golang的协程上下文的具体使用
    go协程上下文context golang的context 主要用来在 goroutine 之间传递上下文信息,包括:取消信号、超时时间、截止时间、k-v 等 context是gol...
    99+
    2024-04-02
  • python上下文管理器协议怎么实现
    这篇文章主要介绍了python上下文管理器协议怎么实现的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇python上下文管理器协议怎么实现文章都会有所收获,下面我们一起来看看吧。前言在上下文管理器协议的过程中,涉...
    99+
    2023-07-02
  • Flask的上下文管理详解
    本篇内容介绍了“Flask的上下文管理详解”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!Flask线程间上下文安全Falsk完成线程安全的原...
    99+
    2023-06-02
  • Flask中的请求上下文和应用上下文对象如何理解
    这篇文章将为大家详细讲解有关Flask中的请求上下文和应用上下文对象如何理解,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。在Flask中处理请求时,应用会生成一个“请求上下文”对象。整个请求...
    99+
    2023-06-17
  • Python编程ContextManager上下文管理器讲解
    目录什么是上下文管理器官方解释简单一句话__enter__(self)__exit__(self, exc_type, exc_value, exc_traceback)有哪些常见上...
    99+
    2024-04-02
  • SpringMVC教程之文件上传与下载详解
    目录前言一、文件上传二、文件下载1.传统方式2.使用ResponseEntity方式前言 文件上传是项目开发中最常见的功能之一 ,SpringMVC 可以很好的支持文件上传,但是Sp...
    99+
    2022-12-08
    SpringMVC文件上传下载 SpringMVC文件上传 SpringMVC文件下载
  • PHP异步协程开发:解决大文件上传与下载的难题
    随着网络技术的发展和应用场景的不断扩展,大文件上传和下载已经成为了许多Web应用面临的难题。传统的处理方式往往耗时较长,效率较低,而PHP异步协程开发则能够有效地解决这些问题。近年来,PHP语言的异步编程技术逐渐得到了广泛的应用,其中协程技...
    99+
    2023-12-18
    PHP 异步协程 大文件传输
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作