async 函数结果获取机制分析

13 阅读4分钟

一、总体结论

async 的返回值最终存放在 JobSupport 的 state 字段中,await() 只是去读这个字段——读到了就直接返回(快路径);读不到就挂起自己,等协程完成回调把自己唤醒后再读一次(慢路径)。结果传递的本质是「共享状态 + 完成回调」,不是消息推送。

二、逐段源码与分析

1. async() 本体——结果的写入端

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
}

分析:async() 本身不碰结果。coroutine.start(start, coroutine, block) 把 block 调度到新协程执行,async() 立即返回 DeferredCoroutine 实例(本质是一个 Job)。block 执行完毕时,由父类 AbstractCoroutine / JobSupport 的状态机把结果(值或异常)通过 CAS 写入 state 字段,state 从 Incomplete 变为完成态——这就是结果的写入端。

2. DeferredCoroutine 类——await 的转发层

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)
}

分析:DeferredCoroutine 自身几乎没有逻辑,await() 只是转发到父类的 awaitInternal()。它同时实现 SelectClause1,使 async 的结果还能参与 select 多路选择(onAwait)。所有核心机制都在父类 JobSupport 的状态机中。

3. awaitInternal()——快路径:一次读取直接拿结果

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()
        }
        if (startInternal(state) >= 0) break
    }
    return awaitSuspend()
}

分析:无锁 while(true) 循环读取 state,分三种情况。一,已完成且正常:state.unboxState() 直接返回结果——一次读取即拿到结果,零挂起、零对象分配,这就是快路径。二,已完成但是 CompletedExceptionally:执行 recoverAndThrow(state.cause) 抛出异常(慢速路径用于恢复堆栈)。三,未完成:startInternal(state) >= 0 时 break,转入 awaitSuspend() 慢路径。

4. awaitSuspend()——慢路径:挂起并注册完成回调

private suspend fun awaitSuspend(): Any? = suspendCoroutineUninterceptedOrReturn { uCont ->
    val cont = AwaitContinuation(uCont.intercepted(), this)
    cont.disposeOnCancellation(invokeOnCompletion(ResumeAwaitOnCompletion(this, cont).asHandler))
    cont.getResult()
}

分析:这是「最后怎么拿到结果」的核心入口,共三步。第一步,suspendCoroutineUninterceptedOrReturn 捕获调用方协程的原始 continuation(uCont)并拦截分发。第二步,包装成 AwaitContinuation,并通过 invokeOnCompletion(ResumeAwaitOnCompletion(this, cont)) 在 Job 上注册完成回调——注释专门说明:这样父协程 await 子 async 协程时,抛出的是子协程的真实异常,而不是 JobCancellationException。第三步,cont.getResult() 决定挂起还是直接拿结果(见下一段)。

5. getResult()——挂起与恢复的枢纽

internal fun getResult(): Any? {
    setupCancellation()
    if (trySuspend()) return COROUTINE_SUSPENDED
    val state = this.state
    if (state is CompletedExceptionally) throw recoverStackTrace(state.cause, this)
    if (resumeMode.isCancellableMode) {
        val job = context[Job]
        if (job != null && !job.isActive) {
            val cause = job.getCancellationException()
            cancelCompletedResult(state, cause)
            throw recoverStackTrace(cause, this)
        }
    }
    return getSuccessfulResult(state)
}

分析:完整时序是五步。第一步,trySuspend() 成功时返回 COROUTINE_SUSPENDED 哨兵值,await 挂起点生效,调用方协程在此让出线程。第二步,async 协程体执行完毕,state 被置为结果,触发 ResumeAwaitOnCompletion 回调,恢复等待者的 continuation。第三步,恢复后 getResult() 重新读取 state:异常态则 throw recoverStackTrace(...)。第四步,父 Job 已取消则抛出取消异常——处理「注册回调与协程完成之间的竞态」:suspendCancellableCoroutine 若在 block 返回前就 cont.resume(...),此处本会错误返回结果而非取消异常。第五步,一切正常则走 getSuccessfulResult(state)。另外 trySuspend() 失败本身也是竞态兜底:说明注册回调时协程恰好已完成,此时直接从 state 拿值返回,不挂起。

6. getSuccessfulResult()——最终取值

override fun <T> getSuccessfulResult(state: Any?): T =
    when (state) {
        is CompletedContinuation -> state.result as T
        else -> state as T
    }

分析:最终取值非常简单——state 是 CompletedContinuation 时取其 result 字段强转为 T(完成态可能同时携带异常与句柄等附加信息);否则普通值直接装箱在 state 上,强转即结果。整个链路到这里收敛:await 的返回值,就是当初 block 执行完毕时 CAS 写入 state 的那个值。

三、可强调的三个机制要点

  1. 结果传递 = 共享状态 + 完成回调,不是消息推送:值挂在 state 上,等待者被唤醒后自己去读。
  2. COROUTINE_SUSPENDED 哨兵协议:挂起函数通过返回这个特殊对象告知协程框架「我没有结果,我挂起了」,这是整个 Kotlin 挂起机制的底座。
  3. 快慢路径双保险:trySuspend() 失败意味着注册回调时协程恰好完成了(竞态),此时 getResult 直接从 state 拿值返回,不挂起。