• Kotlin协程:异步执行与同步获取


    一.async方法

        在协程中,可以通过async方法开启一个协程,执行任务。但与launch方法不同的是,async方法会返回一个Deferred接口指向的对象,通过调用找个对象的await方法,可以获取任务的执行结果,如果这时任务没有结束,await方法还会同步挂起等待任务执行完毕返回结果再继续执行。

        async方法代码如下:

    public fun <T> CoroutineScope.async(
        context: CoroutineContext = EmptyCoroutineContext,
        start: CoroutineStart = CoroutineStart.DEFAULT,
        block: suspend CoroutineScope.() -> T
    ): Deferred<T> {// 返回类型Deferred
        // 计算新的上下文
        val newContext = newCoroutineContext(context)
        // 判断是否为懒启动
        val coroutine = if (start.isLazy)
            LazyDeferredCoroutine(newContext, block) else
            DeferredCoroutine<T>(newContext, active = true)
        // 启动协程
        coroutine.start(start, coroutine, block)
        // 返回
        return coroutine
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

        async方法启动协程的逻辑与launch方法是相同的,launch方法在Kotlin协程:创建、启动、挂起、恢复中分析过。async方法与launch方法唯一不同的就是返回对象的类型,launch方法返回对象的类型为Job,async方法返回对象的类型为Deferred。

    1.Deferred接口

        Deferred接口继承了Job接口,主要定义了await方法,代码如下:

    // 继承Job接口
    public interface Deferred<out T> : Job {
        // 一个挂起方法,用于等待获取最终执行结果
        public suspend fun await(): T
        
        // 配合select方法,实现多路复用
        public val onAwait: SelectClause1<T>
    
        // 实验方法,之后可能删除或重新命名
        // 用于当确定任务已经执行完毕后调用,获取最终的结果
        // 如果任务未执行完毕或已经取消,则会抛出一个异常
        @ExperimentalCoroutinesApi
        public fun getCompleted(): T
    
         
        // 实验方法,之后可能删除或重新命名
        // 用于当确定任务已经执行完毕后调用
        // 如果任务正常执行结束,则返回null
        // 如果任务没有执行完毕,则会抛出一个异常
        // 如果任务被取消,则会返回对应的异常
        @ExperimentalCoroutinesApi
        public fun getCompletionExceptionOrNull(): Throwable?
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

        在默认启动模式下,async方法最终返回的Deferred接口指向的对象DeferredCoroutine类型的对象。

    2.DeferredCoroutine类

        DeferredCoroutine继承自AbstractCoroutine类,实现了Deferred接口与SelectClause1接口,代码如下:

    @Suppress("UNCHECKED_CAST")
    private open class DeferredCoroutine<T>(
        parentContext: CoroutineContext,
        active: Boolean
    ) : AbstractCoroutine<T>(parentContext, active), Deferred<T>, SelectClause1<T> {
        override fun getCompleted(): T = getCompletedInternal() as T
        override suspend fun await(): T = awaitInternal() as T
        override val onAwait: SelectClause1<T> get() = this
        override fun <R> registerSelectClause1(select: SelectInstance<R>, block: suspend (T) -> R) =
            registerSelectClause1Internal(select, block)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

        DeferredCoroutine类内部通过调用父类JobSupport的awaitInternal方法实现了await方法。

    二.await方法

    1.awaitInternal方法

        awaitInternal方法是await方法的核心实现,定义在JobSupport类中,代码入下:

    internal suspend fun awaitInternal(): Any? {
        // 循环检查状态
        while (true) {
            // 获取当前状态
            val state = this.state
            // 如果执行完成
            if (state !is Incomplete) {
                // 如果已经取消,则抛出异常
                if (state is CompletedExceptionally) {
                    recoverAndThrow(state.cause)
                }
                // 返回最终结果 
                return state.unboxState()
    
            }
            // 如果没有执行完成,则启动执行
            // 如果返回的结果为TRUE或FALSE,即不是RETRY,则跳出循环
            // 如果返回RETRY,则会再循环一次,启动执行
            if (startInternal(state) >= 0) break
        }
        // 挂起等待结果并返回
        return awaitSuspend()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

        awaitInternal方法首先会尝试获取执行结果,同时会触发协程的启动。如果任务没有执行完毕,则会通过awaitSuspend方法进入挂起状态,等待任务执行结束。

    2. awaitSuspend方法

        awaitSuspend方法用于挂起当前协程,等待async方法执行完毕后恢复,代码如下:

                                               // 获取续体
    private suspend fun awaitSuspend(): Any? = suspendCoroutineUninterceptedOrReturn { uCont ->
        // 包装成AwaitContinuation续体
        val cont = AwaitContinuation(uCont.intercepted(), this)
        // 注册内部协程执行完毕的回调与外部协程取消的回调
        cont.disposeOnCancellation(invokeOnCompletion(ResumeAwaitOnCompletion(this, cont).asHandler))
        // 尝试获取结果
        cont.getResult()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

        awaitSuspend方法与之前在Kotlin协程:生命周期原理中提到的joinSuspend方法实现原理类似。这里的getResult方法也是之前在Kotlin协程:生命周期原理提到的CancellableContinuationImpl类的方法,首次调用会挂起协程。

        这里需要注意,通常在一般情况下,我们通过async方法启动协程去执行任务,并且在async方法启动的协程的父协程中挂起等待执行结果。awaitSuspend方法中,invokeOnCompletion方法注册的是async方法启动的协程的完成回调,disposeOnCancellation注册的是async方法启动的协程的父协程的取消回调。

    3.ResumeAwaitOnCompletion类

        如果async方法所在协程的父协程被取消,则过程与之前分析的cancel方法的调用路径相同。

        如果async方法所在协程正常的执行完毕,则根据之前的分析,会通过嵌套的调用invoke方法,最终调用到ResumeAwaitOnCompletion类对象的invoke方法,代码如下:

    private class ResumeAwaitOnCompletion<T>(
        job: JobSupport,
        private val continuation: CancellableContinuationImpl<T>
    ) : JobNode<JobSupport>(job) {
        override fun invoke(cause: Throwable?) {
            val state = job.state
            assert { state !is Incomplete }
            // 如果发生取消
            if (state is CompletedExceptionally) {
                // 抛出异常并恢复
                continuation.resumeWithException(state.cause)
            } else {
                // 如果正常完成,则直接恢复,并传入最终结果
                @Suppress("UNCHECKED_CAST")
                continuation.resume(state.unboxState() as T)
            }
        }
        override fun toString() = "ResumeAwaitOnCompletion[$continuation]"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

        这里的continuation对象,就是通过intercepted方法调度后的async方法启动协程的父协程的续体。传入最终结果,就是async方法启动的协程的执行结果。

  • 相关阅读:
    数据结构与算法 - 链表相关面试题
    sklearn笔记:调参
    普冉PY32系列(八) GPIO模拟和硬件SPI方式驱动无线收发芯片XN297LBW
    国外视频搬运素材去哪里找?可搬运下载国外的素材网站库分享
    Neural Collaborative Filtering论文笔记
    Helm Subcharts And Global Values practical operation
    redis的原理和源码-慢查询日志&监视器
    使用IVX来创造一个自己的3D小游戏【后台和中台、React Core、three.js、Pixi.js、Krpano、antD......】
    day04 spring 声明式事务
    PX4模块设计之二十三:自定义FlightTask
  • 原文地址:https://blog.csdn.net/LeeDuoZuiShuai/article/details/126333225