我的编程空间,编程开发者的网络收藏夹
学习永远不晚

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

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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

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

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

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

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

下载Word文档

猜你喜欢

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

这篇文章主要介绍了Kotlin开发中续体与续体拦截器和调度器的相关使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
2022-11-13

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录