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 协程实现原理很有帮助!

相关推荐
XeonYu3 小时前
Kotlin 协程之 Flow 的理解使用及源码解析
kotlin·flow·coroutine
一笑的小酒馆3 小时前
Android性能优化之截屏时黑屏卡顿问题
android
懒人村杂货铺6 小时前
Android BLE 扫描完整实战
android
TeleostNaCl8 小时前
如何安装 Google 通用的驱动以便使用 ADB 和 Fastboot 调试(Bootloader)设备
android·经验分享·adb·android studio·android-studio·android runtime
fatiaozhang95279 小时前
中国移动浪潮云电脑CD1000-系统全分区备份包-可瑞芯微工具刷机-可救砖
android·网络·电脑·电视盒子·刷机固件·机顶盒刷机
低调小一9 小时前
Swift 语法学习指南 - 与 Kotlin 对比
微信·kotlin·swift
2501_9159184110 小时前
iOS 开发全流程实战 基于 uni-app 的 iOS 应用开发、打包、测试与上架流程详解
android·ios·小程序·https·uni-app·iphone·webview
lichong95110 小时前
【混合开发】vue+Android、iPhone、鸿蒙、win、macOS、Linux之dist打包发布在Android工程asserts里
android·vue.js·iphone
Android出海10 小时前
Android 15重磅升级:16KB内存页机制详解与适配指南
android·人工智能·新媒体运营·产品运营·内容运营
一只修仙的猿10 小时前
毕业三年后,我离职了
android·面试