This article thoroughly understands the suspension and recovery of coroutines

This article requires a certain understanding of coroutines, and an in-depth understanding of the suspension and recovery of coroutines from the perspective of source code.

code directly

    GlobalScope.launch {
        val data = goodeTest()
        print(data)
    }
    Thread.sleep(3000)
}

suspend fun goodTest(): String {
    val num = deal1()
    val user = deal2(num)
    val strong = deal3(user)
    return strong
}

suspend fun deal1(): Int {
    delay(22)
    return 2
}

suspend fun deal2(num: Int): User {
    val user = User(11, "1")
    delay(2000)
    return user
}

suspend fun deal3(user: User): String {
    delay(300)
    return "ss"
}

upper bytecode. The learning principle still depends on the bytecode. The bytecode reorganized on the Internet always feels almost meaningless to understand. This time I went directly to the unmodified one, with a step-by-step understanding of how the coroutine suspends and resumes. You can just skip this overview and come back to this overview at the end. I'll explain a little bit later.

package com.zs.myapplication.coroutine;

import com.zs.myapplication.bean.User;
import kotlin.Metadata;
import kotlin.ResultKt;
import kotlin.Unit;
import kotlin.coroutines.Continuation;
import kotlin.coroutines.CoroutineContext;
import kotlin.coroutines.intrinsics.IntrinsicsKt;
import kotlin.coroutines.jvm.internal.Boxing;
import kotlin.coroutines.jvm.internal.ContinuationImpl;
import kotlin.jvm.functions.Function2;
import kotlin.jvm.internal.Intrinsics;
import kotlinx.coroutines.BuildersKt;
import kotlinx.coroutines.CoroutineScope;
import kotlinx.coroutines.CoroutineStart;
import kotlinx.coroutines.DelayKt;
import kotlinx.coroutines.GlobalScope;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

@Metadata(
   mv = {1, 6, 0},
   k = 2,
   d1 = {"\u0000 \n\u0000\n\u0002\u0010\b\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0002\b\u0003\n\u0002\u0010\u000e\n\u0002\b\u0004\n\u0002\u0010\u0002\n\u0000\u001a\u0011\u0010\u0000\u001a\u00020\u0001H\u0086@ø\u0001\u0000¢\u0006\u0002\u0010\u0002\u001a\u0019\u0010\u0003\u001a\u00020\u00042\u0006\u0010\u0005\u001a\u00020\u0001H\u0086@ø\u0001\u0000¢\u0006\u0002\u0010\u0006\u001a\u0019\u0010\u0007\u001a\u00020\b2\u0006\u0010\t\u001a\u00020\u0004H\u0086@ø\u0001\u0000¢\u0006\u0002\u0010\n\u001a\u0011\u0010\u000b\u001a\u00020\bH\u0086@ø\u0001\u0000¢\u0006\u0002\u0010\u0002\u001a\u0006\u0010\f\u001a\u00020\r\u0082\u0002\u0004\n\u0002\b\u0019¨\u0006\u000e"},
   d2 = {"deal1", "", "(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;", "deal2", "Lcom/zs/myapplication/bean/User;", "num", "(ILkotlin/coroutines/Continuation;)Ljava/lang/Object;", "deal3", "", "user", "(Lcom/zs/myapplication/bean/User;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;", "goodTest", "main", "", "My_Application.app.main"}
)
public final class GoodAnalisyKt {
   public static final void main() {
      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) {
            Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
            Object var10000;
            switch(this.label) {
            case 0:
               ResultKt.throwOnFailure($result);
               this.label = 1;
               var10000 = GoodAnalisyKt.goodTest(this);
               if (var10000 == var3) {
                  return var3;
               }
               break;
            case 1:
               ResultKt.throwOnFailure($result);
               var10000 = $result;
               break;
            default:
               throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
            }

            String data = (String)var10000;
            System.out.print(data);
            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);
      Thread.sleep(3000L);
   }

   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }

   @Nullable
   public static final Object goodTest(@NotNull Continuation var0) {
      Object $continuation;
      label37: {
         if (var0 instanceof <undefinedtype>) {
            $continuation = (<undefinedtype>)var0;
            if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) {
               ((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;
               break label37;
            }
         }

         $continuation = new ContinuationImpl(var0) {
            // $FF: synthetic field
            Object result;
            int label;

            @Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
               this.result = $result;
               this.label |= Integer.MIN_VALUE;
               return GoodAnalisyKt.goodTest(this);
            }
         };
      }

      Object var10000;
      label31: {
         Object var6;
         label30: {
            Object $result = ((<undefinedtype>)$continuation).result;
            var6 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
            switch(((<undefinedtype>)$continuation).label) {
            case 0:
               ResultKt.throwOnFailure($result);
               ((<undefinedtype>)$continuation).label = 1;
               var10000 = deal1((Continuation)$continuation);
               if (var10000 == var6) {
                  return var6;
               }
               break;
            case 1:
               ResultKt.throwOnFailure($result);
               var10000 = $result;
               break;
            case 2:
               ResultKt.throwOnFailure($result);
               var10000 = $result;
               break label30;
            case 3:
               ResultKt.throwOnFailure($result);
               var10000 = $result;
               break label31;
            default:
               throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
            }

            int num = ((Number)var10000).intValue();
            ((<undefinedtype>)$continuation).label = 2;
            var10000 = deal2(num, (Continuation)$continuation);
            if (var10000 == var6) {
               return var6;
            }
         }

         User user = (User)var10000;
         ((<undefinedtype>)$continuation).label = 3;
         var10000 = deal3(user, (Continuation)$continuation);
         if (var10000 == var6) {
            return var6;
         }
      }

      String strong = (String)var10000;
      return strong;
   }

   @Nullable
   public static final Object deal1(@NotNull Continuation var0) {
      Object $continuation;
      label20: {
         if (var0 instanceof <undefinedtype>) {
            $continuation = (<undefinedtype>)var0;
            if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) {
               ((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;
               break label20;
            }
         }

         $continuation = new ContinuationImpl(var0) {
            // $FF: synthetic field
            Object result;
            int label;

            @Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
               this.result = $result;
               this.label |= Integer.MIN_VALUE;
               return GoodAnalisyKt.deal1(this);
            }
         };
      }

      Object $result = ((<undefinedtype>)$continuation).result;
      Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
      switch(((<undefinedtype>)$continuation).label) {
      case 0:
         ResultKt.throwOnFailure($result);
         ((<undefinedtype>)$continuation).label = 1;
         if (DelayKt.delay(22L, (Continuation)$continuation) == var3) {
            return var3;
         }
         break;
      case 1:
         ResultKt.throwOnFailure($result);
         break;
      default:
         throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
      }

      return Boxing.boxInt(2);
   }

   @Nullable
   public static final Object deal2(int var0, @NotNull Continuation var1) {
      Object $continuation;
      label20: {
         if (var1 instanceof <undefinedtype>) {
            $continuation = (<undefinedtype>)var1;
            if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) {
               ((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;
               break label20;
            }
         }

         $continuation = new ContinuationImpl(var1) {
            // $FF: synthetic field
            Object result;
            int label;
            Object L$0;

            @Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
               this.result = $result;
               this.label |= Integer.MIN_VALUE;
               return GoodAnalisyKt.deal2(0, this);
            }
         };
      }

      Object $result = ((<undefinedtype>)$continuation).result;
      Object var5 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
      User user;
      switch(((<undefinedtype>)$continuation).label) {
      case 0:
         ResultKt.throwOnFailure($result);
         user = new User(11, "1");
         ((<undefinedtype>)$continuation).L$0 = user;
         ((<undefinedtype>)$continuation).label = 1;
         if (DelayKt.delay(2000L, (Continuation)$continuation) == var5) {
            return var5;
         }
         break;
      case 1:
         user = (User)((<undefinedtype>)$continuation).L$0;
         ResultKt.throwOnFailure($result);
         break;
      default:
         throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
      }

      return user;
   }

   @Nullable
   public static final Object deal3(@NotNull User var0, @NotNull Continuation var1) {
      Object $continuation;
      label20: {
         if (var1 instanceof <undefinedtype>) {
            $continuation = (<undefinedtype>)var1;
            if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) {
               ((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;
               break label20;
            }
         }

         $continuation = new ContinuationImpl(var1) {
            // $FF: synthetic field
            Object result;
            int label;

            @Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
               this.result = $result;
               this.label |= Integer.MIN_VALUE;
               return GoodAnalisyKt.deal3((User)null, this);
            }
         };
      }

      Object $result = ((<undefinedtype>)$continuation).result;
      Object var4 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
      switch(((<undefinedtype>)$continuation).label) {
      case 0:
         ResultKt.throwOnFailure($result);
         ((<undefinedtype>)$continuation).label = 1;
         if (DelayKt.delay(300L, (Continuation)$continuation) == var4) {
            return var4;
         }
         break;
      case 1:
         ResultKt.throwOnFailure($result);
         break;
      default:
         throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
      }

      return "ss";
   }
}

1. goodTest()The bytecode we look at first

@Nullable
public static final Object goodTest(@NotNull Continuation var0) {
   Object $continuation;
   label37: {
      if (var0 instanceof <undefinedtype>) {
         $continuation = (<undefinedtype>)var0;
         if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) {
            ((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;
            break label37;
         }
      }

      $continuation = new ContinuationImpl(var0) {   生成一个continuation类型的匿名类
         // $FF: synthetic field
         Object result;
         int label;

         @Nullable
         public final Object invokeSuspend(@NotNull Object $result) {
            this.result = $result;
            this.label |= Integer.MIN_VALUE;
            return GoodAnalisyKt.goodTest(this);  //自己调自己
         }
      };
   }

   Object var10000;
   label31: {
      Object var6;
      label30: {
         Object $result = ((<undefinedtype>)$continuation).result;
         var6 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
         switch(((<undefinedtype>)$continuation).label) {
         case 0:  
            ResultKt.throwOnFailure($result);
            ((<undefinedtype>)$continuation).label = 1;
            var10000 = deal1((Continuation)$continuation);
            if (var10000 == var6) {
               return var6;
            }
            break;
         case 1:
            ResultKt.throwOnFailure($result);
            var10000 = $result;
            break;
         case 2:
            ResultKt.throwOnFailure($result);
            var10000 = $result;
            break label30;
         case 3:
            ResultKt.throwOnFailure($result);
            var10000 = $result;
            break label31;
         default:
            throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
         }

         int num = ((Number)var10000).intValue();
         ((<undefinedtype>)$continuation).label = 2;
         var10000 = deal2(num, (Continuation)$continuation);
         if (var10000 == var6) {
            return var6;
         }
      }

      User user = (User)var10000;
      ((<undefinedtype>)$continuation).label = 3;
      var10000 = deal3(user, (Continuation)$continuation);
      if (var10000 == var6) {
         return var6;
      }
   }

   String strong = (String)var10000;
   return strong;
}

We know that the start of the coroutine is through the GlobalScop.launch{}method, which will create a suspendLamada, and will also pass down a continuation. So goodTestthe input continuation is suspendLamadapassed in from this, and the method method will be called at startup invokeSuspend(). That is, an overview of the methods in the java file , let's invokeSuspend()take a look, as follows

public static final void main() {
   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) {
         Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
         Object var10000;
         switch(this.label) {
         case 0:
            ResultKt.throwOnFailure($result);
            this.label = 1;
            var10000 = GoodAnalisyKt.goodTest(this);
            if (var10000 == var3) {
               return var3;
            }
            break;
         case 1:
            ResultKt.throwOnFailure($result);
            var10000 = $result;
            break;
         default:
            throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
         }

         String data = (String)var10000;
         System.out.print(data);
         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);
   Thread.sleep(3000L);
}

This block will call the method, then call the invokeSuspend()method according to the label state machine and pass it . This is wrapped again , created by calling the method as followscase 0goodTest()continutioncontiuntioncompletioncompletionGlobalScope.launch{}StandaloneCoroutine

This StandaloneCoroutineis one AbstractCoroutine, and the truth will be used in the later recovery. Next goodTest(), after entering the switchstatement case 0, call the deal1()method, and then pass in a few sentences continuationthat continuationwrap the parent coroutine to contiunationread and write. Then look at the deal1()method, you will also create an anonymous class to integrate from ContinuationImpland continutionaccept goodTest()the passed down continution, and then enter the same case0, call delay()the function, pass in the method you created ContinuationImpl, and then return to the method that COROUTINE_SUSPENDEDwill be called for a while , this time is the recovery, the key point Let's take a look at the implementation class of the methodcontinuationresumeWith()resumeWith()

So hang is hang in this block. This block is also called when it needs to be restored.

Remember delay()which method we passed in continution? Yes, deal1()the anonymous class created in the method inherits from ContinuationImpl, so this block currentis its own anonymous class continution, that is, in 1addition. deal1()Therefore, the method's own method will be called suspendInvoke(), because at the beginning 2, we will conpletionassign an assignment to ourselves, and replace the one to be operated with continutionthe one passed in by ourselves, continutionwhich is the parent coroutine continution, so deal1()after recovery, the next cycle The method of invokeSuspendthe parent coroutine invokeSuspend()is called, and for our demo, it is goodTest()the invokeSuspend()method called, which completes the flow of the state. When this method is finally called, that is, when the goodTest()method calls the resumeWith()method, what is passed in is AbstractCoroutineso will enter 标注6and complete the recovery of the entire coroutine.

At this point, the summary of the coroutine suspension recovery principle is completed. To summarize:

  1. When the coroutine starts (calls launch), there will be two important ones Continution, one is AbstractCoroutineand the other is suspendLamada(suspendLamada inherits from BaseContinuationImpl)

  2. When the suspendfunction is called, an anonymous class ContinuationImpl(inherited from BaseContinuationImpl) is created and the parent coroutine is contiuntionpassed in, and the suspension and recovery are all controlled BaseContinuationImplinside .resumeWith()while(true)

  3. When the function suspends (that is, the function returns COROUTINE_SUSPENDED), BaseContinuationImplreturn inside.

  4. When the function is restored, it is also BaseContinuationImplcalled inside resumeWith, and it will be replaced by continutionthe parent coroutine continution. invokeSuspendStart the flow of the completion event of the parent coroutine .

  5. Finally , the method of the AbstractCoroutinetype is called to complete the recovery of the entire coroutinecontinutionresumeWith()

Author: Lazurs
Link: https://juejin.cn/post/7147534071978491934
Source: Rare Earth Nuggets

Guess you like

Origin blog.csdn.net/m0_64420071/article/details/127055506