Kotlin图文并茂讲解续体与续体拦截器和调度器

目录
  • 一.Continuation
  • 二.ContinuationInterceptor
  • 三.CoroutineDispatcher
  • 四.EventLoop

一.Continuation

Continuation接口是协程中最核心的接口,代表着挂起点之后的续体,代码如下:

public interface Continuation<in T> {
    // 续体的上下文
    public val context: CoroutineContext
    // 该方法用于恢复续体的执行
    // result为挂起点执行完成的返回值,T为返回值的类型
    public fun resumeWith(result: Result<T>)
}

Continuation图解

二.ContinuationInterceptor

ContinuationInterceptor接口继承自Element接口,是协程中的续体拦截器,代码如下:

public interface ContinuationInterceptor : CoroutineContext.Element {
    // 拦截器的Key
    companion object Key : CoroutineContext.Key<ContinuationInterceptor>
    // 拦截器对续体进行拦截时会调用该方法,并对continuation进行缓存
    // 拦截判断:根据传入的continuation对象与返回的continuation对象是否相同
    public fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T>
    // 当interceptContinuation方法拦截的协程执行完毕后,会调用该方法
    public fun releaseInterceptedContinuation(continuation: Continuation<*>) {
        /* do nothing by default */
    }
    // get方法多态实现
    public override operator fun <E : CoroutineContext.Element> get(key: CoroutineContext.Key<E>): E? {
        @OptIn(ExperimentalStdlibApi::class)
        if (key is AbstractCoroutineContextKey<*, *>) {
            @Suppress("UNCHECKED_CAST")
            return if (key.isSubKey(this.key)) key.tryCast(this) as? E else null
        }
        @Suppress("UNCHECKED_CAST")
        return if (ContinuationInterceptor === key) this as E else null
    }
    // minusKey方法多态实现
    public override fun minusKey(key: CoroutineContext.Key<*>): CoroutineContext {
        @OptIn(ExperimentalStdlibApi::class)
        if (key is AbstractCoroutineContextKey<*, *>) {
            return if (key.isSubKey(this.key) && key.tryCast(this) != null) EmptyCoroutineContext else this
        }
        return if (ContinuationInterceptor === key) EmptyCoroutineContext else this
    }
}

三.CoroutineDispatcher

CoroutineDispatcher类继承自AbstractCoroutineContextElement类,实现了ContinuationInterceptor接口,是协程调度器的基类,代码如下:

public abstract class CoroutineDispatcher :
    AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
    // ContinuationInterceptor的多态实现,调度器本质上就是拦截器
    @ExperimentalStdlibApi
    public companion object Key : AbstractCoroutineContextKey<ContinuationInterceptor, CoroutineDispatcher>(
        ContinuationInterceptor,
        { it as? CoroutineDispatcher })
    // 用于判断调度器是否要调用dispatch方法进行调度,默认为true
    public open fun isDispatchNeeded(context: CoroutineContext): Boolean = true
    // 调度的核心方法,在这里进行调度,执行block
    public abstract fun dispatch(context: CoroutineContext, block: Runnable)
    // 如果调度是由Yield方法触发的,默认通过dispatch方法实现
    @InternalCoroutinesApi
    public open fun dispatchYield(context: CoroutineContext, block: Runnable): Unit = dispatch(context, block)
    // ContinuationInterceptor接口的方法,将续体包裹成DispatchedContinuation,并传入当前调度器
    public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
        DispatchedContinuation(this, continuation)
    // 释放父协程与子协程的关联。
    @InternalCoroutinesApi
    public override fun releaseInterceptedContinuation(continuation: Continuation<*>) {
        (continuation as DispatchedContinuation<*>).reusableCancellableContinuation?.detachChild()
    }
    // 重载了"+"操作,直接返回others
    // 因为两个调度器相加没有意义,同一个上下文中只能有一个调度器
    // 如果需要加的是调度器对象,则直接替换成最新的,因此直接返回
    public operator fun plus(other: CoroutineDispatcher): CoroutineDispatcher = other
    override fun toString(): String = "$classSimpleName@$hexAddress"
}

四.EventLoop

EventLoop类继承自CoroutineDispatcher类,用于协程中任务的分发执行,只在runBlocking方法中和Dispatchers.Unconfined调度器中使用。与Handler中的Looper类似,在创建后会存储在当前线程的ThreadLocal中。EventLoop本身不支持延时执行任务,如果需要可以自行继承EventLoop并实现Delay接口,EventLoop中预留了一部分变量和方法用于延时需求的扩展。

为什么协程需要EventLoop呢?协程的本质是续体传递,而续体传递的本质是回调,假设在Dispatchers.Unconfined调度下,要连续执行多个suspend方法,就会有多个续体传递,假设suspend方法达到一定数量后,就会造成StackOverflow,进而引起崩溃。同样的,我们知道调用runBlocking会阻塞当前线程,而runBlocking阻塞的原理就是执行“死循环”,因此需要在循环中做任务的分发,去执行内部协程在Dispatchers.Unconfined调度器下加入的任务。

EventLoop代码如下:

internal abstract class EventLoop : CoroutineDispatcher() {
    // 用于记录使用当前EventLoop的runBlocking方法和Dispatchers.Unconfined调度器的数量
    private var useCount = 0L
    // 表示当前的EventLoop是否被暴露给其他的线程
    // runBlocking会将EventLoop暴露给其他线程
    // 因此,当runBlocking使用时,shared必须为true
    private var shared = false
    // Dispatchers.Unconfined调度器的任务执行队列
    private var unconfinedQueue: ArrayQueue<DispatchedTask<*>>? = null
    // 处理任务队列的下一个任务,该方法只能在EventLoop所在的线程调用
    // 返回值<=0,说明立刻执行下一个任务
    // 返回值>0,说明等待这段时间后,执行下一个任务
    // 返回值为Long.MAX_VALUE,说明队列里没有任务了
    public open fun processNextEvent(): Long {
        if (!processUnconfinedEvent()) return Long.MAX_VALUE
        return 0
    }
    // 队列是否为空
    protected open val isEmpty: Boolean get() = isUnconfinedQueueEmpty
    // 下一个任务多长时间后执行
    protected open val nextTime: Long
        get() {
            val queue = unconfinedQueue ?: return Long.MAX_VALUE
            return if (queue.isEmpty) Long.MAX_VALUE else 0L
        }
    // 任务的核心处理方法
    public fun processUnconfinedEvent(): Boolean {
        // 若队列为空,则返回
        val queue = unconfinedQueue ?: return false
        // 从队首取出一个任务,如果为空,则返回
        val task = queue.removeFirstOrNull() ?: return false
        // 执行
        task.run()
        return true
    }
    // 表示当前EventLoop是否可以在协程上下文中被调用
    // EventLoop本质上也是协程上下文
    // 如果EventLoop在runBlocking方法中使用,必须返回true
    public open fun shouldBeProcessedFromContext(): Boolean = false
    // 向队列中添加一个任务
    public fun dispatchUnconfined(task: DispatchedTask<*>) {
        // 若队列为空,则创建一个新的队列
        val queue = unconfinedQueue ?:
            ArrayQueue<DispatchedTask<*>>().also { unconfinedQueue = it }
        queue.addLast(task)
    }
    // EventLoop当前是否还在被使用
    public val isActive: Boolean
        get() = useCount > 0
    // EventLoop当前是否还在被Unconfined调度器使用
    public val isUnconfinedLoopActive: Boolean
        get() = useCount >= delta(unconfined = true)
    // 判断队列是否为空
    public val isUnconfinedQueueEmpty: Boolean
        get() = unconfinedQueue?.isEmpty ?: true
    // 下面三个方法用于计算使用当前的EventLoop的runBlocking方法和Unconfined调度器的数量
    // useCount是一个64位的数,
    // 它的高32位用于记录Unconfined调度器的数量,低32位用于记录runBlocking方法的数量
    private fun delta(unconfined: Boolean) =
        if (unconfined) (1L shl 32) else 1L
    fun incrementUseCount(unconfined: Boolean = false) {
        useCount += delta(unconfined)
        // runBlocking中使用,shared为true
        if (!unconfined) shared = true
    }
    fun decrementUseCount(unconfined: Boolean = false) {
        useCount -= delta(unconfined)
        // 如果EventLoop还在被使用
        if (useCount > 0) return
        assert { useCount == 0L }
        // 如果EventLoop不被使用了,并且在EventLoop中使用过
        if (shared) {
            // 关闭相关资源,并在ThreadLocal中移除
            shutdown()
        }
    }
    protected open fun shutdown() {}
}

协程中提供了EventLoopImplBase类,间接继承自EventLoop,实现了Delay接口,用来延时执行任务。同时,协程中还提供单例对象ThreadLocalEventLoop用于EventLoop在ThreadLocal中的存储。

到此这篇关于Kotlin图文并茂讲解续体与续体拦截器和调度器的文章就介绍到这了,更多相关Kotlin续体内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Android的OkHttp包中的HTTP拦截器Interceptor用法示例

    OkHttp(GitHub:https://github.com/square/okhttp) 的 Interceptor 就如同名称「拦截器」一样,拦截你的 Request 做一些你想做的事情再送出去.例如: 1.自动加上使用者目前使用的语言送出去取得对应语言的回传内容. 2.将 Request 计算出这个 Request 的 sigunature 再附加上送出去. 在 okHttp 中分成 Application Interceptor 和 Network Interceptor 两种. A

  • Android OKHttp3拦截器的使用方法

    本文介绍了Android OKHttp3拦截器的使用方法,分享给大家,具体如下: 添加Interceptor 在上一篇中我们已经知道了okhttp的基本使用,其中在介绍OkHttpClient初始化的时候,介绍了两种方式,第二种方式就可以对这个OkHttpClient对象设置拦截器,如下所示: // 配置一些信息进入OkHttpClient mOkHttpClient = new OkHttpClient().newBuilder() .connectTimeout(REQUEST_TIME,

  • Android 后台调度任务与省电详解

    I. Handler: 在进程存活的期间有效使用, Google官方推荐使用. 简单易用. 稳定高效. II. AlarmManager: 利用系统层级的闹钟服务(持有Wake lock). 如果需要精确的定时任务,这个是最佳选择. 1. 功能 在大概的时间间隔 运行/重复执行 指定任务. 指定精确的时间间隔执行任务. 2. 特征 注册以后,无论是自己的应用进程是否存在/组件是否存在,都会正常执行. 所有注册的闹钟服务都会在系统重启后复位,因此如果需要保证任务,就需要注册RECEIVE_BOOT

  • Kotlin图文并茂讲解续体与续体拦截器和调度器

    目录 一.Continuation 二.ContinuationInterceptor 三.CoroutineDispatcher 四.EventLoop 一.Continuation Continuation接口是协程中最核心的接口,代表着挂起点之后的续体,代码如下: public interface Continuation<in T> { // 续体的上下文 public val context: CoroutineContext // 该方法用于恢复续体的执行 // result为挂起

  • C++基础入门教程(三):数组、字符串、结构体、共用体

    今天的标题取得..好严肃的感觉.(小若:咳噗) 这章的内容虽然还是很详(lao)细(dao),但已经开始有很多值得记录的内容了~ 那么,今天就来初次介绍数组与字符串-以及结构体..还有共用体..吧. 1.数组 我记得大四实习的时候,请教同事:"什么是属主?"(其实是和数据库相关的东西) 然后同事惊讶地说道:"啊,你连数组都不知道..这,基础还是要好好补补-呐,数组的意思呢,是这样的-" 我听着听着就不对劲,"等等,这是数组-其实我是问这个属主-"

  • C语言中结构体和共用体实例教程

    目录 一.实验目的 二.实验内容 三.实验记录 3.1 候选人选票统计 3.2 print函数 3.3 链表 总结 一.实验目的 掌握结构体类型变量的定义和使用: 掌握结构体类型数组的概念和应用: 掌握链表的概念,初步学会对链表进行操作: 掌握共用体的概念与使用: 掌握指向结构体变量的指针. 掌握指向结构体数组的指针的应用. 二.实验内容 编写下列程序,然后上机调试运行. 对候选人得票的统计程序.设有3个候选人,每次输入一个得票的候选人的名字,要求最后输出各人得票结果. 编写一个函数print,

  • c# 如何使用结构体实现共用体

    目录 理解 C 语言的共用体 使用 C# 实现共用体 共用体作为另一个共用体的成员 在 C 和 C# 编程语言中,结构体(Struct)是值类型数据结构,它使得一个单一变量可以存储多种类型的相关数据.在 C 语言中还有一种和结构体非常类似的语法,叫共用体(Union),有时也被直译为联合或者联合体.而在 C# 中并没有共用体这样一个定义,本文将介绍如何使用 C# 实现 C 语言中的共用体. 理解 C 语言的共用体 在 C 语言中,共用体是一种特殊的数据类型,允许你使用相同的一段内存空间存储不同的

  • 解析C/C++指针、函数、结构体、共用体

    目录 指针 变量与地址 指针与指针变量 占内存空间 指针运算 指针 变量与地址 变量给谁用的?变量是对某一块空间的抽象命名.变量名就是你抽象出来的某块空间的别名.指针就是地址.指向某个地址. 指针与指针变量 指针是指向某块地址.指针(地址)是常量.指针变量是可以发生变化的. #include <stdio.h> int main() { int i = 1; int *p = &i; printf("i = %d \n", i); printf("&

  • Java 图文并茂讲解两种找二叉树的最近公共祖先的方法

    目录 思路一:先假设这棵树是二叉搜索树 思路二:假设该树是用孩子双亲表示法 思路一:先假设这棵树是二叉搜索树 首先我们补充说明一下什么是二叉搜索树: 在二叉搜索树中,对于每一个节点来说,他的左子树中的值都比他小,右子树的中的值都比他大.所以二叉搜索树的中序遍历是一组有序的数据. 对于上述这棵树,假设要求 p q 的最近公共祖先. 那么它有以下情况: 对于普通的二叉树来说,也无非就这几种情况:pq都在左,pq都在右,pq一左一右,pq有一个是根节点. 所以分别递归的去左子树和右子树中找 p q 节

  • C语言图文并茂讲解分支语句用法

    目录 一.if 语句分析 二.switch 语句分析 三.小结 一.if 语句分析 if 语句用于根据条件选择执行语句 else 不能独立存在且总是与它最近的 if 相匹配 else 语句后可以接连其他 if 语句 if 语句中零值比较的注意点 bool 型变量应该直接出现于条件中,不要进行比较 变量和 0 值比较时,0 值应该出现在比较符号左边(这条规则可以拓展为任意字面量与变量比较时,字面量应该放在左边,变量放在右边,这样即使手误写成了 = ,编译器也能发现) float 型变量不能直接进行

  • C++图文并茂讲解类型转换函数

    目录 一.类型转换函数(上) 1.再论类型转换 2.问题 3.再论构造函数 4.另一个视角 5.编译器的行为 6.小结(上) 二.类型转换函数(下) 1.类型转换 2.编译器的行为 3.注意事项 4.小结(下) 一.类型转换函数(上) 1.再论类型转换 标准数据类型之间会进行隐式的类型安全转换 转换规则如下: 下面来看一个有趣的隐式类型转换: #include <iostream> #include <string> using namespace std; int main()

  • C++图文并茂讲解继承

    目录 一.生活中的例子 二.惊艳的继承 三.继承的意义 四.小结 一.生活中的例子 组合关系∶整体与部分的关系 下面看一个组合关系的描述代码: #include <iostream> #include <string> using namespace std; class Memory { public: Memory() { cout << "Memory()" << endl; } ~Memory() { cout <<

  • SpringBoot图文并茂讲解依赖管理的特性

    目录 1.父依赖parent介绍 2.修改默认版本号 3.starter场景启动器 1.父依赖parent介绍 pom文件中含有父依赖 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.4.RELEASE</version> </pa

随机推荐