协程原理再议

Posted 北洋~

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了协程原理再议相关的知识,希望对你有一定的参考价值。

关于协程的一些理解

协程挂起让异步代码可以像同步代码一样调用,但其本质还是同步,即协程体中的代码其实是同步。

因为协程也只是对线程池的封装,所以需要了解些线程的一些知识。线程本身已经有的协程也会有,但是协程有的线程不一定有

编译器会为每一个挂起函数生成一个匿名内部类,其继承SuspendLabmba类重写其invokeSuspend方法,这个方法里面即为协程体的代码【大致内容请先了解】

编译器会对协程体中的挂起函数和普通函数进行切割,切割时进行label的自增来保证之后代码的执行顺序,即协程保证运行顺序的本质。【对比线程:进行PC程序计数器的控制来恢复执行】

协程体中会添加一个label字段,标识接下来该运行协程体中的哪行代码【即协程如何知道自己执行到哪步是用这个label完成的】(对比线程:线程存储下一个代码指令是用PC计数器来做的)

协程体中的数据,看过之前那篇文章的人应该大致了解挂起函数其实就是匿名内部类,数据是保存在栈帧中的,(对比线程:也是通过栈帧中的局部变量表和操作数栈来存储数据)

协程体中碰到挂起函数会直接返回,等待挂起函数通知

当我们调用挂起函数时都会传入一个Continuation,挂起函数执行完正常退出或者抛异常退出这个时候外面的协程需要知道这个信息,怎么通知呢?通过Continuation的resumewith方法,这个方法会再次调用invokeSuspend取出label来保证之后执行代码的顺序,即协程自动恢复运行的本质

(对比线程:方法正常执行完成之后有两种情况1、正常结束,2.异常退出。其里面的PC计数器保证之后执行的代码顺序,也就是说协程在原本线程之上又加了一层控制)

协程体中可以在开启一个协程,也就是协程具有父子关系的本质。在协程伊始的时候会默认给一些默认数据(包括协程体运行在哪个线程即调度器其通过拦截器实现,执行状态检测用到的Job,拦截器等等)这些数据保存在协程的上下文中,

当在协程体中又开启了一个协程时,其会获取父协程的上下文进行和自己的合并作为自己的上下文。

【简而言之,和协程本身有关的数据保存在上下文中,和业务有关的代码会放在匿名内部类中。】

基础框架层源码分析

val createCoroutine = suspend 
                //挂起函数代码

            .createCoroutine(object : Continuation<Unit> 
                override val context: CoroutineContext
                    get() = TODO("Not yet implemented")

                override fun resumeWith(result: Result<Unit>) 
					//可从result中获取返回值或异常
                
            )

   createCoroutine.resume(Unit)

1.编译器会对挂起函数做处理,让他继承SuspendCorunting,重写其invokeSuspend方法,方法体为挂起函数中的代码
2.当手动调用createCoroutine返回值的resume方法的时候,会调用到resumeWith函数,其里面会调用invokeSuspend也就是挂起函数体里面的代码,之后两种情况:
3.碰到普通函数直接执行;
碰到挂起函数传入Continuation并直接返回一个标识代表其为挂起函数,这个时候协程直接退出,之后挂起函数执行完调用Continuation的resumeWith继续执行方法体代码【通过label确定顺序】
4.在invokeSuepend中可以通过result获取挂起函数执行的结果:异常或者返回值。进行对应处理。

实战分析

class ExampleUnitTest 
    @Test
    fun addition_isCorrect() 
        GlobalScope.launch 
            println("挂起点1开始")
            delay(1000)         //挂起点1
            println("挂起点1结束")
            hello()             //挂起点2
            println("挂起点2结束")
            delay(1000)         //挂起点3
            println("挂起点3结束")
            word()              //挂起点4
        
    
    //挂起函数,编译器默认传入Continuation
    suspend fun hello()
        //再次挂起
        withContext(Dispatchers.IO)
            delay(1000)
            println("hello")
        
    
    
    suspend fun word()
        withContext(Dispatchers.IO)
            delay(1000)
            println("word")
        
    

反编译后代码分析

public final class ExampleUnitTest 
   @Test
   public final void addition_isCorrect() 
      Assert.assertEquals(4L, 4L);

      BuildersKt.launch$default((CoroutineScope)GlobalScope.INSTANCE, (CoroutineContext)null, (CoroutineStart)null, (Function2)(new Function2((Continuation)null) 
         int label;

         @Nullable
         public final Object invokeSuspend(@NotNull Object $result) 
            ExampleUnitTest var10000;
            String var2;
            boolean var3;
            Object var4;
            //对应的标志位
            label34: 
               label33: 
                  var4 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
                  switch(this.label) 
                  case 0:
                     ResultKt.throwOnFailure($result);
                     var2 = "挂起点1开始";
                     var3 = false;
                     System.out.println(var2);
                     this.label = 1;
                     //根据返回值判断是否为挂起函数,如果为挂起函数直接返回
                     // 挂起函数会持有Continuation,挂起函数执行后调用其resumeWith方法,之后就会回到invokeSuspend中
                     if (DelayKt.delay(1000L, this) == var4) 
                        //协程退出
                        return var4;
                     
                     break;
                  case 1:
                     //检测上一步也就是delay是否发生异常或者失败,接着执行-----》挂起点1结束
                     ResultKt.throwOnFailure($result);
                     break;
                  case 2:
                     //检测上一步也就是hello是否发生异常或者失败,接着执行-----》挂起点2结束
                     ResultKt.throwOnFailure($result);
                     break label33;
                  case 3:
                     //检测上一步也就是delay是否发生异常或者失败,接着执行-----》挂起点3结束
                     ResultKt.throwOnFailure($result);
                     break label34;
                  case 4:
                     ResultKt.throwOnFailure($result);
                     return Unit.INSTANCE;
                  default:
                     throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
                  

                  var2 = "挂起点1结束";
                  var3 = false;
                  //打印
                  System.out.println(var2);
                  var10000 = ExampleUnitTest.this;
                  //label增加,表示上一步执行完成,指向下一个该执行的函数体
                  this.label = 2;
                  //检测hello是否为挂起函数
                  if (var10000.hello(this) == var4) 
                     //协程退出
                     return var4;
                  
               

               //退出label33后执行的函数
               var2 = "挂起点2结束";
               var3 = false;
               System.out.println(var2);
               //再次更正
               this.label = 3;
               //检测是否为挂起函数
               if (DelayKt.delay(1000L, this) == var4) 
                  //直接退出协程
                  return var4;
               
            
            //退出label34后执行的函数
            var2 = "挂起点3结束";
            var3 = false;
            System.out.println(var2);
            var10000 = ExampleUnitTest.this;
            this.label = 4;
             //检测是否为挂起函数
            if (var10000.word(this) == var4) 
               return var4;
             else 
               return Unit.INSTANCE;
            
         


         @NotNull
         public final Continuation create(@Nullable Object value, @NotNull Continuation completion) 
            Intrinsics.checkNotNullParameter(completion, "completion");
            Function2 var3 = new <anonymous constructor>(completion);
            return var3;
         

         public final Object invoke(Object var1, Object var2) 
            return ((<undefinedtype>)this.create(var1, (Continuation)var2)).invokeSuspend(Unit.INSTANCE);
         
      ), 3, (Object)null);
   


看完上面的反编译解析会发现还差了两个方法,没错,接下来就是自定的挂起函数的反编译代码,根据上面的分析请读者亲自分析下接下来的这两个函数


@Nullable
public final Object hello(@NotNull Continuation $completion) 
   Object var10000 = BuildersKt.withContext((CoroutineContext)Dispatchers.getIO(), (Function2)(new Function2((Continuation)null) 
      int label;

      @Nullable
      public final Object invokeSuspend(@NotNull Object $result) 
      Object var4 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
      switch(this.label) 
         case 0:
         ResultKt.throwOnFailure($result);
         this.label = 1;
         if (DelayKt.delay(1000L, this) == var4) 
            return var4;
         
         break;
         case 1:
         ResultKt.throwOnFailure($result);
         break;
         default:
         throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
      

      String var2 = "hello";
      boolean var3 = false;
      System.out.println(var2);
      return Unit.INSTANCE;
   

      @NotNull
      public final Continuation create(@Nullable Object value, @NotNull Continuation completion) 
      Intrinsics.checkNotNullParameter(completion, "completion");
      Function2 var3 = new <anonymous constructor>(completion);
      return var3;
   

      public final Object invoke(Object var1, Object var2) 
      return ((<undefinedtype>)this.create(var1, (Continuation)var2)).invokeSuspend(Unit.INSTANCE);
   
   ), $completion);
   return var10000 == IntrinsicsKt.getCOROUTINE_SUSPENDED() ? var10000 : Unit.INSTANCE;


@Nullable
public final Object word(@NotNull Continuation $completion) 
   Object var10000 = BuildersKt.withContext((CoroutineContext)Dispatchers.getIO(), (Function2)(new Function2((Continuation)null) 
      int label;

      @Nullable
      public final Object invokeSuspend(@NotNull Object $result) 
      Object var4 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
      switch(this.label) 
         case 0:
         ResultKt.throwOnFailure($result);
         this.label = 1;
         if (DelayKt.delay(1000L, this) == var4) 
            return var4;
         
         break;
         case 1:
         ResultKt.throwOnFailure($result);
         break;
         default:
         throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
      

      String var2 = "word";
      boolean var3 = false;
      System.out.println(var2);
      return Unit.INSTANCE;
   

      @NotNull
      public final Continuation create(@Nullable Object value, @NotNull Continuation completion) 
      Intrinsics.checkNotNullParameter(completion, "completion");
      Function2 var3 = new <anonymous constructor>(completion);
      return var3;
   

      public final Object invoke(Object var1, Object var2) 
      return ((<undefinedtype>)this.create(var1, (Continuation)var2)).invokeSuspend(Unit.INSTANCE);
   
   ), $completion);
   return var10000 == IntrinsicsKt.getCOROUTINE_SUSPENDED() ? var10000 : Unit.INSTANCE;


总而言之,言而总之。还是上一篇博客总结的道理,就是不断的invokeSuspend标记标志位和挂起函数调用resumeWith。

文末致辞:

感谢东方月初提供的资料,和在分析协程原理时给予的支持。

以上是关于协程原理再议的主要内容,如果未能解决你的问题,请参考以下文章

Python中的协程与asyncio原理

Python中的协程与asyncio原理

Python中的协程与asyncio原理

聊一聊Unity协程背后的实现原理

聊一聊Unity协程背后的实现原理

聊一聊Unity协程背后的实现原理