在专栏的开篇,我们仔细剖析了 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 协程实现原理很有帮助!