我在另一篇博客 Kotlin的协程 已经介绍了关于协程的很多东西,也是这一篇能看懂的基础。这一篇专门用一个示例来讲解一下它的底层运行机制。
在一个KotlinShow.kt文件里有如下代码
suspend fun example() {
println("start")
delay(1000) // 挂起点1
println("middle")
delay(500) // 挂起点2
println("end")
}
@OptIn(DelicateCoroutinesApi::class)
fun main() = runBlocking {
val job = GlobalScope.launch {
example()
}
job.join()
}
使用IDEA的decompile后看到的代码
IDEA里decompile后的代码如下。为方便指定位置,我在下面的代码里插入了注释,如第A处、第B处等。
@Metadata(
mv = {2, 2, 0},
k = 2,
xi = 48,
d1 = {"\u0000\n\n\u0000\n\u0002\u0010\u0002\n\u0002\b\u0003\u001a\u000e\u0010\u0000\u001a\u00020\u0001H\u0086@¢\u0006\u0002\u0010\u0002\u001a\u0006\u0010\u0003\u001a\u00020\u0001¨\u0006\u0004"},
d2 = {"example", "", "(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;", "main", "Sources of Practice"}
)
public final class KotlinShowKt {
@Nullable
public static final Object example(@NotNull Continuation $completion) {
Continuation $continuation;
label27: {
if ($completion instanceof <undefinedtype>) {
$continuation = (<undefinedtype>)$completion;
if (($continuation.label & Integer.MIN_VALUE) != 0) {
$continuation.label -= Integer.MIN_VALUE;
break label27;
}
}
$continuation = new ContinuationImpl($completion) {
// $FF: synthetic field
Object result;
int label;
//第J处
@Nullable
public final Object invokeSuspend(@NotNull Object $result) {
this.result = $result;
this.label |= Integer.MIN_VALUE;
return KotlinShowKt.example((Continuation)this);
}
};
}
label22: {
Object $result = $continuation.result;
Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch ($continuation.label) {
//第A处
case 0:
ResultKt.throwOnFailure($result);
System.out.println("start");
$continuation.label = 1;
if (DelayKt.delay(1000L, $continuation) == var3) {
return var3;
}
break;
case 1:
ResultKt.throwOnFailure($result);
break;
case 2:
ResultKt.throwOnFailure($result);
break label22;
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
System.out.println("middle");
$continuation.label = 2;
//第B处
if (DelayKt.delay(500L, $continuation) == var3) {
return var3;
}
}
System.out.println("end");
return Unit.INSTANCE;
}
public static final void main() {
BuildersKt.runBlocking$default((CoroutineContext)null, new Function2((Continuation)null) {
Object L$0;
int label;
//第C处
public final Object invokeSuspend(Object $result) {
Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch (this.label) {
case 0:
ResultKt.throwOnFailure($result);
//第D处
Job job = BuildersKt.launch$default((CoroutineScope)GlobalScope.INSTANCE, (CoroutineContext)null, (CoroutineStart)null, new Function2((Continuation)null) {
int label;
//第E处
public final Object invokeSuspend(Object $result) {
Object var2 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch (this.label) {
//第F处
case 0:
ResultKt.throwOnFailure($result);
Continuation var10000 = (Continuation)this;
this.label = 1;
//第G处
if (KotlinShowKt.example(var10000) == var2) {
return var2;
}
break;
case 1:
ResultKt.throwOnFailure($result);
break;
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
return Unit.INSTANCE;
}
public final Continuation create(Object value, Continuation $completion) {
return (Continuation)(new <anonymous constructor>($completion));
}
public final Object invoke(CoroutineScope p1, Continuation p2) {
return ((<undefinedtype>)this.create(p1, p2)).invokeSuspend(Unit.INSTANCE);
}
// $FF: synthetic method
// $FF: bridge method
public Object invoke(Object p1, Object p2) {
return this.invoke((CoroutineScope)p1, (Continuation)p2);
}
}, 3, (Object)null);
Continuation var10001 = (Continuation)this;
this.L$0 = SpillingKt.nullOutSpilledVariable(job);
this.label = 1;
//第H处
if (job.join(var10001) == var3) {
return var3;
}
break;
case 1:
Job job = (Job)this.L$0;
ResultKt.throwOnFailure($result);
break;
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
return Unit.INSTANCE;
}
public final Continuation create(Object value, Continuation $completion) {
return (Continuation)(new <anonymous constructor>($completion));
}
public final Object invoke(CoroutineScope p1, Continuation p2) {
return ((<undefinedtype>)this.create(p1, p2)).invokeSuspend(Unit.INSTANCE);
}
// $FF: synthetic method
// $FF: bridge method
public Object invoke(Object p1, Object p2) {
return this.invoke((CoroutineScope)p1, (Continuation)p2);
}
}, 1, (Object)null);
}
// $FF: synthetic method
public static void main(String[] args) {
main();
}
}
在反编译出的代码中可以看到example()被编译成了一个static方法。经过多次尝试,我发现先把眼光聚焦在这个example()上更容易把协程讲清楚。咱们先不管程序到底是怎样启动起来的,现在咱们只需知道example()方法要第一次执行了。
可以看到在执行example()时会传进来一个Continuation,然后又用它生成了一个ContinuationImpl,再往下就会执行到第A处代码,其中有delay,但它并不会真的要等多少ms才能返回结果和var3判断是否相等,它会立即返回结果,所以这里其实马上就和var3做了是否相等的判断,首次执行时为true,所以这里很快就返回了一个SUSPENDED的结果,example()首次执行也就结束了。
delay为什么能马上就返回呢?因为它相当于提交了一个定时任务,但是并不由它去执行这个定时任务,定时任务由调度器去执行,delay()提交之后就马上返回了,再看下它的第二个参数DelayKt.delay(1000L, $continuation),
$continuation ---
continuation = new ContinuationImpl(completion) {
// $FF: synthetic field
Object result;
int label;
//第J处
@Nullable
public final Object invokeSuspend(@NotNull Object $result) {
this.result = $result;
this.label |= Integer.MIN_VALUE;
return KotlinShowKt.example((Continuation)this);
}
};
调度器方面大致如下
scheduler.schedule(1000ms, continuation)
当定时到达后,调度器里面就会调用$continuation的resumeWith() ---> invokeSuspend(),example()就会又被调用,但此时lable已不是0了,这就很清晰地展现出了协程的机制------状态驱动。每次传给example()的Continuation不一样,example()也就会有不同的逻辑,当最后一个Continuation处理完,example()的整个逻辑也就执行完毕了。每次传进来的Continuation是 恢复执行的上下文 + 入口 。
理清了example()的执行规律,再类推外层的逻辑,对于嵌套型的协程也就更容易理解了。
main()中用BuildersKt.runBlocking$default(编译后的带默认参数的runBlocking)启动了协程,通过查询API的调用流程就可以发现最后经过 BaseContinuationImpl.resumeWith() 调用了 main()中的
public final Object invokeSuspend(Object $result),
即 第C处invokeSuspend(代码中有标记注释)
此时lable为默认值0,所以程序进入了case 0分支,其中的 第D处 代码
Job job = BuildersKt.launch$default((CoroutineScope)GlobalScope.INSTANCE... 就是源代码中的 GlobalScope.launch(),launch只是提交一个协程,并不一定会立即就能执行,所以这个方法会立即返回,invokeSuspend会继续往下走,直到第H处 job.join() ,join()会将launch协程挂起,注意:join()也是一个suspend函数,它会立即返回,由于返回的是"挂起",所以launch此次的invokeSuspend就会马上执行结束。同样查询API,又可以发现这个launch最终又是通过resumeWith()走到了 第E处invokeSuspend,同样,label为0,第G处代码触发example()执行。那么example()第一次执行时的Continuation是谁传进去的呢?正是launch的,launch的Continuation又是哪里来的呢?是runBlocking的,这样Continuation就把多处协程串联起来了。当example()彻底执行完毕,调度器就会通过Continuation触发launch再次执行,以此类推,直至最外层协程执行完毕。调度器总是不停触发最底层的Continuation来推动协程从最底层层层向上完成。
join() 本身不等待。它只看一眼 job 是否已完成:
如果已完成 → 立刻返回,啥也不干。
如果未完成 → 把当前协程的"剩余代码"打包成一个回调,注册到 job 上,然后 立刻返回一个"我已挂起"的信号。
挂起就是提前返回。join() 返回挂起信号后,外层调用 join() 的那个函数(协程的状态机)也立刻返回。整个协程的执行函数走完了这一次调用,不再占用任何线程。
恢复靠回调。当 job 执行完毕时,它触发之前注册的回调,这个回调会 再次进入 协程的状态机,从上次挂起的地方继续往下执行。