Ali P7 boss teaches you the implementation principle of Kotlin coroutine: suspend and resume

Implementation principle of Kotlin coroutine

Today we are going to talk about Kotlincoroutines Coroutine.

If you have been exposed to coroutines, I believe you have the following questions:

  1. What exactly is a coroutine?
  2. What is the role of the coroutine suspend, and what is the working principle?
  3. What is the relationship between some key names (such as: Job, Coroutine, Dispatcher, CoroutineContextand ) in the coroutine?CoroutineScope
  4. What is the so-called non-blocking suspension and recovery of coroutines?
  5. What is the internal implementation principle of the coroutine?

The next few articles try to analyze these questions, and everyone is welcome to join in the discussion.

hang up

Coroutines use non-blocking suspension to ensure that coroutines run. So what is non-blocking suspend? Let's talk about what kind of operation it is to hang up.

The keyword was mentioned in the previous article suspend. One of its functions is that when the code is called, a Continuationtype parameter will be added to the method to ensure the up and down transfer in the coroutine Continuaton.

Another key role of it is to identify the suspended coroutine.

Whenever a modified method is encountered when the coroutine is running suspend, the current coroutine may be suspended.

Attention is possible.

You can write a method at will, and the method can also be suspendmodified, but this method will not be suspended when called in a coroutine. For example

private suspend fun a() {
 println("aa")
}

lifecycleScope.launch {
 a()
}

Because this method does not return COROUTINE_SUSPENDEDa type.

The flag that the coroutine is suspended is the return flag in the corresponding state COROUTINE_SUSPENDED.

Going a little deeper involves state machines . Inside the coroutine, a state machine is used to manage each suspension point of the coroutine.

The text is a bit abstract, let's look at the code specifically. Let's take the above amethod as an example to illustrate.

First Android Studioopen this code Kotlin Bytecode. can be Tools -> Kotlin -> Show Kotlin Bytecodeopened in .

Then click one of Decompilethe options to generate the corresponding decompiled javacode. The final code is as follows:

BuildersKt.launch$default((CoroutineScope)LifecycleOwnerKt.getLifecycleScope(this), (CoroutineContext)null, (CoroutineStart)null, (Function2)(new Function2((Continuation)null) {
   private CoroutineScope p$;
   Object L$0;
   int label;
 
   @Nullable
   public final Object invokeSuspend(@NotNull Object $result) {
      // 挂起标识
      Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
      CoroutineScope $this$launch;
      switch(this.label) {
      case 0:
         ResultKt.throwOnFailure($result);
         $this$launch = this.p$;
         MainActivity var10000 = MainActivity.this;
         // 保存现场
         this.L$0 = $this$launch;
         // 设置挂起后恢复时,进入的状态
         this.label = 1;
         // 判断是否挂起
         if (var10000.a(this) == var3) {
            // 挂起,跳出该方法
            return var3;
         }
         // 不需要挂起,协程继续执行其他逻辑
         break;
      case 1:
         // 恢复现场
         $this$launch = (CoroutineScope)this.L$0;
         // 是否需要抛出异常
         ResultKt.throwOnFailure($result);
         break;
      default:
         throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
      }

      return Unit.INSTANCE;
   }
 
   @NotNull
   public final Continuation create(@Nullable Object value, @NotNull Continuation completion) {
      Intrinsics.checkParameterIsNotNull(completion, "completion");
      Function2 var3 = new <anonymous constructor>(completion);
      var3.p$ = (CoroutineScope)value;
      return var3;
   }
 
   public final Object invoke(Object var1, Object var2) {
      return ((<undefinedtype>)this.create(var1, (Continuation)var2)).invokeSuspend(Unit.INSTANCE);
   }
}), 3, (Object)null);

The above code is the state machine of the coroutine, which is used to labelrepresent different states, so as to execute different caselogic codes correspondingly.

As mentioned in the previous article, when the coroutine is started, the resumeWithmethod will be called manually once, and its corresponding internal logic is to execute the above invokeSuspendmethod.

So when the coroutine is run for the first time, labelthe value is 0, enters case 0:the statement. At this time, the record site prepares for the state that may be suspended, and sets the next state that may be executed.

If athe method returns a value var3, this var3corresponds to it COROUTINE_SUSPENDED. So the internal statement will be executed only when athe method returns , and the method will be jumped out, and the coroutine will be suspended at this time. The current thread can also execute other logic, and will not be blocked by the suspension of the coroutine.COROUTINE_SUSPENDEDif

Therefore, at the code level, the suspension of the coroutine is to jump out of the method body of the coroutine execution, or jump out of the corresponding state under the current state machine of the coroutine, and then wait for the next state to come and execute it.

So why do we say that the method we wrote awill not be suspended?

@Nullable
final Object a(@NotNull Continuation $completion) {
   return Unit.INSTANCE;
}

It turns out that its return value is not COROUTINE_SUSPENDED.

Since it will not be suspended, under what circumstances will the method be suspended?

It's simple, if we aput delaya method in the method, it will be suspended.

@Nullable
final Object a(@NotNull Continuation $completion) {
   Object var10000 = DelayKt.delay(1000L, $completion);
   return var10000 == IntrinsicsKt.getCOROUTINE_SUSPENDED() ? var10000 : Unit.INSTANCE;
}

It's the method that actually triggers the hang delay, because delaythe method creates itself Continuationwhile internally calling getResultthe method.

 internal fun getResult(): Any? {
     installParentCancellationHandler()
     if (trySuspend()) return COROUTINE_SUSPENDED
     // otherwise, onCompletionInternal was already invoked & invoked tryResume, and the result is in the state
     val state = this.state
     if (state is CompletedExceptionally) throw recoverStackTrace(state.cause, this)
     return getSuccessfulResult(state)
 }

In getResultthe method, it will be trySuspendjudged to suspend the current coroutine. Suspending the coroutine of the parent class is triggered by suspending its own coroutine.

If it's just for testing, you can let athe method return directlyCOROUTINE_SUSPENDED

 private suspend fun a(): Any {
     return kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
 }

Of course, you must not write like this online, because once you write the coroutine like this, it will always be suspended, because you have no ability to resume it.

recover

Now let's talk about the recovery of coroutines.

The recovery essence of the coroutine is triggered by the method Continuation.resumeWith

Let's look at an example that can be suspended, and use it to analyze the entire process of coroutine suspension and recovery.

println("main start")
lifecycleScope.launch {
   println("async start")
   val b = async {
       delay(2000)
       "async"
   }
   b.await()
   println("async end")
}
Handler().postDelayed({
   println("main end")
}, 1000)

KotlinThe code is very simple. The current coroutine is running in the main thread, and a asyncmethod is executed internally awaitto trigger the suspension of the coroutine.

Let's look at its corresponding decompiled javacode

// 1
String var2 = "main start";
System.out.println(var2);
BuildersKt.launch$default((CoroutineScope)LifecycleOwnerKt.getLifecycleScope(this), (CoroutineContext)null, (CoroutineStart)null, (Function2)(new Function2((Continuation)null) {
   private CoroutineScope p$;
   Object L$0;
   Object L$1;
   int label;
 
   @Nullable
   public final Object invokeSuspend(@NotNull Object $result) {
      Object var5 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
      CoroutineScope $this$launch;
      Deferred b;
      switch(this.label) {
      case 0:
         // 2
         ResultKt.throwOnFailure($result);
         $this$launch = this.p$;
         String var6 = "async start";
         System.out.println(var6);
         b = BuildersKt.async$default($this$launch, (CoroutineContext)null, (CoroutineStart)null, (Function2)(new Function2((Continuation)null) {
            private CoroutineScope p$;
            Object L$0;
            int label;
 
            @Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
               Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
               CoroutineScope $this$async;
               switch(this.label) {
               case 0:
                  // 3
                  ResultKt.throwOnFailure($result);
                  $this$async = this.p$;
                  this.L$0 = $this$async;
                  this.label = 1;
                  if (DelayKt.delay(2000L, this) == var3) {
                     return var3;
                  }
                  break;
               case 1:
                  // 5、6
                  $this$async = (CoroutineScope)this.L$0;
                  ResultKt.throwOnFailure($result);
                  break;
               default:
                  throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
               }
 
               return "async";
            }
 
            @NotNull
            public final Continuation create(@Nullable Object value, @NotNull Continuation completion) {
               Intrinsics.checkParameterIsNotNull(completion, "completion");
               Function2 var3 = new <anonymous constructor>(completion);
               var3.p$ = (CoroutineScope)value;
               return var3;
            }
 
            public final Object invoke(Object var1, Object var2) {
               return ((<undefinedtype>)this.create(var1, (Continuation)var2)).invokeSuspend(Unit.INSTANCE);
            }
         }), 3, (Object)null);
         this.L$0 = $this$launch;
         this.L$1 = b;
         this.label = 1;
         if (b.await(this) == var5) {
            return var5;
         }
         break;
      case 1:
         // 7
         b = (Deferred)this.L$1;
         $this$launch = (CoroutineScope)this.L$0;
         ResultKt.throwOnFailure($result);
         break;
      default:
         throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
      }
 
      // 8
      String var4 = "async end";
      System.out.println(var4);
      return Unit.INSTANCE;
   }
 
   @NotNull
   public final Continuation create(@Nullable Object value, @NotNull Continuation completion) {
      Intrinsics.checkParameterIsNotNull(completion, "completion");
      Function2 var3 = new <anonymous constructor>(completion);
      var3.p$ = (CoroutineScope)value;
      return var3;
   }
 
   public final Object invoke(Object var1, Object var2) {
      return ((<undefinedtype>)this.create(var1, (Continuation)var2)).invokeSuspend(Unit.INSTANCE);
   }
}), 3, (Object)null);
// 4
(new Handler()).postDelayed((Runnable)null.INSTANCE, 1000L);

It's a bit long, but it doesn't matter. We only look at the key points and the content related to its state machine.

  1. First, it will outputmain start , and then launchenter the coroutine state machine by creating a coroutine, at this time label, 0execute case: 0the relevant logic.
  2. After case: 0entering , output**async start** , call asyncand pass awaitto suspend the current coroutine, record the data of the current suspension point during the suspension process, and lableset it to 1.
  3. Enter asyncthe created coroutine, at this time asyncin the coroutine lableis 0, enter the coroutine that is async case: 0executed dealyand suspended . asyncand labelset to 1. Woke up after waiting 2s.
  4. At this time, the coroutines are all suspended, that is, jump out of the coroutine launchmethod and perform handleroperations. Since it is shorter post 1sthan in the coroutine , it will output first , and then go through and enter the recovery coroutine stagedealy**main end**1s
  5. asyncThe coroutine in is delayresumed. Note that the object of delayis passed in the method this, so once the internal timing is completed, the method will be called to resume the coroutine in, that is, call the method.asyncContinuationdelay2sContinuationresumeWithasyncinvokeSuspend
  6. async labelSince it has been set to before being suspended 1, it enters case: 1, resumes the previously suspended site, checks for exceptions, and finally returns async.
  7. At this time await, the suspension point is restored. Note that it is also passed in this, and the corresponding one is launchin the middle Continuation, so the method will be called back resumeWith, and finally called invokeSuspend, that is, enter case 1:the recovery scene and end the state machine.
  8. Finally, continue to outputasync end , and the coroutine operation ends.

We can execute the above code to verify that the output is correct

main start
async start
main end
async end

Let's sum up, the coroutine is used to suspendidentify the suspension point, but the real suspension point needs to be COROUTINE_SUSPENDEDjudged by whether it returns, and the code reflects the suspension and recovery of the coroutine through the state machine. When you need to suspend, first keep the scene and set the next state point, and then suspend the coroutine by exiting the method. The current thread is not blocked during the suspension. The corresponding recovery passes resumeWithto enter the next state of the state machine, and at the same time, the previously suspended scene will be resumed when entering the next state.

This article mainly introduces the principle of suspending and resuming the coroutine, and also analyzes the execution process related to the state machine of the coroutine. I hope it will be helpful to those who are learning coroutines, and please look forward to the follow-up coroutine analysis.

If you need more Kotlin learning materials, you can scan the QR code to get them for free!

" The most detailed Android version of kotlin coroutine entry advanced combat in history "

Chapter 1 Introduction to the Basics of Kotlin Coroutines

​ ● What is a coroutine

​ ● What is Job, Deferred, and coroutine scope

​ ● Basic usage of Kotlin coroutines

img

Chapter 2 Preliminary Explanation of Key Knowledge Points of Kotlin Coroutine

​ ● Coroutine Scheduler

​ ● Coroutine context

​ ● Coroutine startup mode

​ ● Coroutine scope

​ ● suspend function

img

Chapter 3 Exception Handling of Kotlin Coroutines

​ ● Generation process of coroutine exception

​ ● Exception handling for coroutines

img

Chapter 4 Basic application of kotlin coroutines in Android

Guess you like

Origin blog.csdn.net/Android_XG/article/details/129729479