Kotlin协程的运行原理

我在另一篇博客 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 执行完毕时,它触发之前注册的回调,这个回调会 再次进入 协程的状态机,从上次挂起的地方继续往下执行。

相关推荐
z落落18 分钟前
C# ToCharArray + foreach遍历 + String与StringBuilder
开发语言·c#
学代码的真由酱33 分钟前
Java多用户一对一网页聊天室-测试报告
java·开发语言·功能测试·测试
人道领域36 分钟前
【LeetCode刷题日记】669.修剪二叉搜索树
开发语言·python·算法
xiaoshuaishuai81 小时前
C# AvaloniaUI动态显示图片
开发语言·c#
日光明媚2 小时前
一步生成视频!One-Forcing:DMD + 零成本 GAN,训练 200 步超越多步 SOTA
android·开发语言·kotlin
2301_803538952 小时前
Java读取Word图片的两种实用方法
java·开发语言·word
帅次2 小时前
Android 17 开发者实战:核心更新与应用场景落地指南
android·java·ios·android studio·iphone·android jetpack·webview
大鹏说大话3 小时前
SQL 排序与分组实战:解决“分组后取最新数据“
android·java·数据库
plainGeekDev3 小时前
Android运行时面试题:ART和JVM的区别都搞不清,别写精通了
jvm·面试·kotlin
bug和崩溃我都要3 小时前
Qt 封装 libmpv 全功能视频播放器开发指南
开发语言·qt·音视频