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 goodTest
the input continuation is suspendLamada
passed 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 0
goodTest()
continution
contiuntion
completion
completion
GlobalScope.launch{}
StandaloneCoroutine
This StandaloneCoroutine
is one AbstractCoroutine
, and the truth will be used in the later recovery. Next goodTest()
, after entering the switch
statement case 0
, call the deal1()
method, and then pass in a few sentences continuation
that continuation
wrap the parent coroutine to contiunation
read and write. Then look at the deal1()
method, you will also create an anonymous class to integrate from ContinuationImpl
and continution
accept 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_SUSPENDED
will be called for a while , this time is the recovery, the key point Let's take a look at the implementation class of the methodcontinuation
resumeWith()
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 current
is its own anonymous class continution
, that is, in 1
addition. deal1()
Therefore, the method's own method will be called suspendInvoke()
, because at the beginning 2
, we will conpletion
assign an assignment to ourselves, and replace the one to be operated with continution
the one passed in by ourselves, continution
which is the parent coroutine continution
, so deal1()
after recovery, the next cycle The method of invokeSuspend
the 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 AbstractCoroutine
so will enter 标注6
and complete the recovery of the entire coroutine.
At this point, the summary of the coroutine suspension recovery principle is completed. To summarize:
-
When the coroutine starts (calls
launch
), there will be two important onesContinution
, one isAbstractCoroutine
and the other issuspendLamada
(suspendLamada inherits fromBaseContinuationImpl
) -
When the
suspend
function is called, an anonymous classContinuationImpl
(inherited fromBaseContinuationImpl
) is created and the parent coroutine iscontiuntion
passed in, and the suspension and recovery are all controlledBaseContinuationImpl
inside .resumeWith()
while(true)
-
When the function suspends (that is, the function returns
COROUTINE_SUSPENDED
),BaseContinuationImpl
return inside. -
When the function is restored, it is also
BaseContinuationImpl
called insideresumeWith
, and it will be replaced bycontinution
the parent coroutinecontinution
.invokeSuspend
Start the flow of the completion event of the parent coroutine . -
Finally , the method of the
AbstractCoroutine
type is called to complete the recovery of the entire coroutinecontinution
resumeWith()
Author: Lazurs
Link: https://juejin.cn/post/7147534071978491934
Source: Rare Earth Nuggets