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

相关推荐
三少爷的鞋11 小时前
Java 线程自上而下,Kotlin 协程自下而上
android
好的佩奇13 小时前
Dart 之增强枚举
android·flutter·dart
梦影_13 小时前
MySQL进阶教程(五)—— SQL调优(下)
android·大数据·数据库·后端·sql·mysql·database
好的佩奇16 小时前
蓝牙之无线电与通信距离
android·flutter·蓝牙
vilya16 小时前
我把一套完整的 Python Agent 运行时塞进手机 APK:依赖闭包、Rust 轮子和三个真机坑
android
呆呆敲代码的小Y18 小时前
【实战精选面试题】C# 面试题详细解答,按难易程度排序
android·java·游戏·c#·游戏开发·unity面试题·c#面试题
思想觉悟20 小时前
SEAndroid实战
android·linux·c++
思想觉悟20 小时前
Aosp开发磨刀石之ADB速查
android·c++
我是唐青枫21 小时前
Kotlin Inheritance(继承)详解:语法、原理、实战 Demo
开发语言·kotlin
应用市场1 天前
用本机跑的开源音乐模型,给安卓小游戏做 20 秒无缝循环背景音乐
android·python·游戏开发·soundpool·音频处理