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

相关推荐
踢球的打工仔42 分钟前
PHP面向对象(7)
android·开发语言·php
安卓理事人1 小时前
安卓socket
android
安卓理事人7 小时前
安卓LinkedBlockingQueue消息队列
android
万能的小裴同学8 小时前
Android M3U8视频播放器
android·音视频
q***57748 小时前
MySql的慢查询(慢日志)
android·mysql·adb
JavaNoober9 小时前
Android 前台服务 "Bad Notification" 崩溃机制分析文档
android
城东米粉儿9 小时前
关于ObjectAnimator
android
zhangphil10 小时前
Android渲染线程Render Thread的RenderNode与DisplayList,引用Bitmap及Open GL纹理上传GPU
android
火柴就是我11 小时前
从头写一个自己的app
android·前端·flutter
饕餮争锋12 小时前
Kotlin: [Internal Error] java.lang.NoSuchFieldError: FILE_HASHING_STRATEGY
java·kotlin