Kotlin 协程源代码泛读:async

在专栏的开篇,我们仔细剖析了 launch 这个协程构造器(builder),这篇文章再来看看它的一个孪生兄弟 功能比 launch 还强悍一点:async

async 的定义如下

kotlin 复制代码
public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T> {
    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
}

对比 launch 的定义

kotlin 复制代码
public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

来找找茬:

  • launch 的 block 参数是 Unit,没有返回值;async 的 block 参数返回模板参数T
  • launch 创建的协程的类型为 StandaloneCoroutine,async 创建的协程为 DeferredCorotuine
  • launch 返回 Job(接口),async 返回 Deferred 接口

再来看看共同点:

  • Deferrred 接口继承自 Job 接口
    对 Job 接口的一些操作对 Deferred 都是有效的,Deferred 接口提供了一个额外的方法 await,可以等待 block 执行完毕返回 T
  • launch 和 async 最终都是调用基类 AbstractCoroutine 的 start 方法
kotlin 复制代码
public fun <R> start(start: CoroutineStart, receiver: R, block: suspend R.() -> T) {
    start(block, receiver, this)
}

start 方法 block 参数是支持返回值的, Unit 在 Kotlin 也是一个 Object!

所以 launch 和 aysnc 在内部执行逻辑上是一模一样的,区别在于 await 方法(废话)

await 可以挂起当前协程,等待协程执行结果,那么 await 是如何实现挂起当前线程的呢?

kotlin 复制代码
private open class DeferredCoroutine<T>(
    parentContext: CoroutineContext,
    active: Boolean
) : AbstractCoroutine<T>(parentContext, true, active = active), Deferred<T> {
    ...
    override suspend fun await(): T = awaitInternal() as T
    ...
}

awaitInternal 方法定义在爷爷类 JobSupport 中,它也是一个 suspend 函数

你可能会觉得 suspend 函数不是挂起函数吗? 所以 await 能挂起协程那不是正常的吗?

kotlin 复制代码
protected suspend fun awaitInternal(): Any? {
    // fast-path -- check state (avoid extra object creation)
    while (true) { // lock-free loop on state
        val state = this.state
        if (state !is Incomplete) {
            // already complete -- just return result
            if (state is CompletedExceptionally) { // Slow path to recover stacktrace
                recoverAndThrow(state.cause)
            }
            return state.unboxState()
        }
        if (startInternal(state) >= 0) break // break unless needs to retry
    }
    return awaitSuspend() // slow-path
}

哈哈! 老板们稍安勿躁,我们来看看这个 awaitSuspend 函数

复制代码
private suspend fun awaitSuspend(): Any? = suspendCoroutineUninterceptedOrReturn { uCont ->
        /*
         * Custom code here, so that parent coroutine that is using await
         * on its child deferred (async) coroutine would throw the exception that this child had
         * thrown and not a JobCancellationException.
         */
        val cont = AwaitContinuation(uCont.intercepted(), this)
        // we are mimicking suspendCancellableCoroutine here and call initCancellability, too.
        cont.initCancellability()
        cont.disposeOnCancellation(invokeOnCompletion(handler = ResumeAwaitOnCompletion(cont)))
        cont.getResult()
    }

这里出现了另一个非常神奇的函数 suspendCoroutineUnterceptedOrReturn,这个编译器实现的方法,用于获取 suspend 函数里 continuation 参数(移步专栏里关于 suspend 函数/Continuation 的分享)

这个 continuation 有啥用咯? 还记得之前聊过 continuation 是对 suspend 函数内部状态状态机的封装,continuation.resume 方法可以激活状态机!Continuation、suspend 函数、协程、Job 这些概念又再一次被串联在了一起!

Job 在执行完毕后会通知 handler,恢复 continuation,也就是唤醒 await

ini 复制代码
invokeOnCompletion(handler = ResumeAwaitOnCompletion(cont))
kotlin 复制代码
private class ResumeAwaitOnCompletion<T>(
    private val continuation: CancellableContinuationImpl<T>
) : JobNode() {
    override fun invoke(cause: Throwable?) {
        val state = job.state
        assert { state !is Incomplete }
        if (state is CompletedExceptionally) {
            // Resume with with the corresponding exception to preserve it
            continuation.resumeWithException(state.cause)
        } else {
            // Resuming with value in a cancellable way (AwaitContinuation is configured for this mode).
            @Suppress("UNCHECKED_CAST")
            // 恢复 continuation !!!
            continuation.resume(state.unboxState() as T)
        }
    }
}

通过 suspendCorotuineXXX 获取 suspend 函数 continuation 这种模式在框架中很常见

那么 suspendCoroutineUnterceptedOrReturn 是如何获取 suspend fun 内部的 continuation 呢?

我们以 suspendCoroutine 为例,还是使用反编译大法,考虑下面这段代码

kotlin 复制代码
suspend fun testSuspendCoroutine() {
    val name = suspendCoroutine<String> { c ->
        log("suspendCoroutine start")
        val thread = Thread() {
            c.resumeWith(Result.success("lily"))
            log("suspendCoroutine resumed")
        }
        thread.start()
    }
    log("hello $name")
}

反编译成 Java 代码后

ini 复制代码
@Nullable
public static final Object testSuspendCoroutine(@NotNull Continuation $completion) {
      Continuation $continuation;
      label24: {
         ...
         $continuation = new ContinuationImpl($completion) {
             ...
         };
      }
      Object $result = $continuation.result;
      Object var9 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
      Object var10000;
      switch ($continuation.label) {
         case 0:
            ResultKt.throwOnFailure($result);
            $continuation.label = 1;
            // $continuation 就是编译器生成的 suspend 函数内部的 continuation(状态机)
            Continuation var2 = $continuation;
            // suspendCoroutine 是个 inline 函数,所以它的实现被 inline 到了这里!
            SafeContinuation var3 = new SafeContinuation(IntrinsicsKt.intercepted(var2));
            final Continuation c = (Continuation)var3;
            int var5 = 0;
            ContinuationKt.log("suspendCoroutine start");
            Thread thread = new Thread(new Runnable() {
               public final void run() {
                  Result.Companion var10001 = Result.Companion;
                  c.resumeWith(Result.constructor-impl("lily"));
                  ContinuationKt.log("suspendCoroutine resumed");
               }
            });
            thread.start();
            // 在 thread run 方法 resume 之前,getOrThrow 返回 COROUTINE_SUSPEND,也就是挂起!!!
            var10000 = var3.getOrThrow();
            if (var10000 == IntrinsicsKt.getCOROUTINE_SUSPENDED()) {
               DebugProbesKt.probeCoroutineSuspended($continuation);
            }
            if (var10000 == var9) {
               return var9;
            }
            break;
         case 1:
            ResultKt.throwOnFailure($result);
            var10000 = $result;
            break;
         default:
            throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
      }
      String name = (String)var10000;
      ContinuationKt.log("hello " + name);
      return Unit.INSTANCE;
   }

总结: 本文没有详细分析 async 的调用栈,它和之前分析过的 launch 一模一样,不同点在于可以通过 await 获取协程执行结果,所以重点分析了 await 的实现方法,通过 await 的分析,相信可以加深对 Continuation, Coroutine, Job, suspend 函数的理解

Continuation 是基石,理解它对理解 Kotlin 协程实现原理很有帮助!

相关推荐
CatShitK21 分钟前
【Android】 如何将 APK 内置为系统应用(适用于编辑设置属性)
android·java·linux
casual_clover25 分钟前
Android 中实现 GIF 图片动画
android
程序员Linc1 小时前
PP-OCR的安卓端部署
android·ocr·pp-ocr·安卓部署
{⌐■_■}2 小时前
【MySQL】索引运算与NULL值问题详解:索引字段应尽量 NOT NULL ,NULL值不能参与部分索引运算
android·数据库·mysql
移动开发者1号3 小时前
System.currentTimeMillis()与elapsedRealtime()区别
android
顾林海3 小时前
Android Retrofit原理解析
android·面试·源码
V少年3 小时前
深入浅出安卓Jetpack组件
android
老码识土3 小时前
Kotlin 协程源代码泛读:Continuation 思想实验
android·kotlin
划水哥~4 小时前
Activity之间交互
android·kotlin