Today we’re going to talk about Kotlin’s Coroutine.
If you haven’t been exposed to coroutines, I recommend reading this introductory article What? You don’t know Kotlin Coroutine?
If you have been exposed to coroutines and are confused about the principles of coroutines, it is recommended that you read the following articles before reading this article so that you can understand it more thoroughly and smoothly.
Kotlin coroutine implementation principle :Suspend&CoroutineContext
Kotlin coroutine implementation principle :Coroutine type &Job
Kotlin coroutine implementation principle :ContinuationInterceptor&CoroutineDispatcher
If you’ve been around coroutines, you’ve probably had the following questions:
- What the hell is a coroutine?
- coroutines
suspend
What does it do? How does it work? - Some key names in coroutines (e.g.
Job
,Coroutine
,Dispatcher
,CoroutineContext
withCoroutineScope
) What is the relationship between them? - What is the so-called non-blocking suspend and resume of coroutines?
- What is the internal implementation of coroutines?
- .
The next few articles will try to analyze these questions, and you are welcome to join the discussion.
hang
A coroutine is guaranteed to run using a non-blocking suspend. So what is a non-blocking suspend? Now let’s talk about what a hang is.
The suspend keyword was mentioned in the previous article. One of its functions is to add a continuation-type argument to a method when the code is called, allowing Continuaton to pass up and down the coroutine.
Its other key role is to play a suspended coroutine identification.
It is possible to suspend the current coroutine every time it encounters a method decorated by suspend.
Note that it is possible.
You can write any method that is also decorated by suspend, but it is not suspended when called from a coroutine. For example,
private suspend fun a() {
println("aa")
}
lifecycleScope.launch {
a()
}
Copy the code
This method does not return the COROUTINE_SUSPENDED type.
A suspended coroutine returns the COROUTINE_SUSPENDED flag in the corresponding state.
A little deeper involves state machines. Inside the coroutine is the use of a state machine to manage the various hang points of the coroutine.
The text is a little abstract, so let’s look at the code. Let’s take the example of method A above.
Start by opening Kotlin Bytecode for this code in Android Studio. To open it, go to Tools -> Kotlin -> Show Kotlin Bytecode.
Then click on the Decompile option to generate the decompiled Java code. 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) 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; this.label = 1; If (var10000. A (this) == var3) {return var3; } // No need to suspend, the coroutine continues to execute other logic breaks; 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);Copy the code
The code above is the state machine of the coroutine, representing different states by label, thus executing the logical code in different cases.
As described in the previous article, the coroutine starts with a manual call to the resumeWith method, whose internal logic is to execute the invokeSuspend method above.
So the label value is 0 when the coroutine is first run and case 0: is entered. At this point, the site is recorded to prepare for the possible pending state and set the next possible state to be executed.
If method A returns var3, the var3 corresponds to COROUTINE_SUSPENDED. Therefore, only when a method returns COROUTINE_SUSPENDED will the if internal statement be executed to exit the method, at which point the coroutine is suspended. The current thread can execute other logic without being blocked by the suspension of the coroutine.
So the suspension of a coroutine at the code level is to jump out of the method body of the coroutine execution, or jump out of the corresponding state in the current state machine of the coroutine, and then wait for the next state to execute.
So why doesn’t this method a that we wrote get suspended?
@Nullable
final Object a(@NotNull Continuation $completion) {
return Unit.INSTANCE;
}
Copy the code
Its return value is not COROUTINE_SUSPENDED.
Since it will not be suspended, under what circumstances will a method be suspended?
Very simply, if we add the delay method to method A, 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;
}
Copy the code
It is the Delay method that really triggers the suspension, because the Delay method creates its own Continuation and internally calls the getResult 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)
}
Copy the code
The current coroutine is suspended by trySuspend in the getResult method. Triggers the suspension of the parent class’s coroutine by suspending its own coroutine.
For testing purposes, you can have method A return COROUTINE_SUSPENDED directly
private suspend fun a(): Any {
return kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
}
Copy the code
Of course lines should never be written like this, because once written like this the coroutine will always be suspended because you have no ability to recover it.
restore
Now let’s talk a little bit more about coroutine recovery.
The recovery nature of coroutines is triggered by the resumeWith method of continuations.
Let’s look at a suspending example 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)
Copy the code
The Kotlin code is very simple. While the coroutine is running with the main thread, an async method is executed internally and the suspension of the coroutine is triggered with an await method.
Take a look at its decompiled Java counterpart
// 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. 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);Copy the code
It’s a little long, it doesn’t matter but let’s just look at the key points, look at the state machine.
- First of all beThe output
main start
And then throughlaunch
Create coroutine, enter coroutine state machine, at this pointlabel
for0
, the implementation ofcase: 0
Correlation logic. - Enter the
case: 0
afterThe outputasync start
, the callasync
And through theawait
To suspend the current coroutine, record the data of the current point of suspension during resuspend, and willlable
Set to1
. - Enter the
async
Create the coroutine at this timeasync
Coroutines inlable
for0
And into theasync case: 0
performdealy
And hangasync
Coroutines. And will belabel
Set to1
. Waiting for the2s
And then they wake up. - All coroutines are suspended, that is, coroutines are jumped
launch
Method, executionhandler
Operation. Due to thepost 1s
So compared to coroutinesdealy
It’s short, so it’s a priorityThe outputmain end
And then over1s
To enter the recovery coroutine phase async
The coroutine indelay
Recovery, pay attention todelay
Method is passed inthis
.async
theContinuation
Object, sodelay
Once the interior is complete2s
The timer will be calledContinuation
theresumeWith
Methods to recoverasync
The coroutine, that is, the callinvokeSuspend
Methods.- Due to being suspended before already will
async label
Set to1
, so entercase: 1
, resuming the site before suspending, checking for anomalies, and finally returningasync
. - At this time
await
The hang point is restored, notice that it is also passed inthis
Theta corresponds to thetalaunch
In theContinuation
, so there will be a callbackresumeWith
Method, the final callinvokeSuspend
, that is, to entercase 1:
Restore the scene and end the state machine. - Finally, async end is output, and the coroutine is finished.
We can execute the above code to verify that the output is correct
main start
async start
main end
async end
Copy the code
To summarize, the point of suspension of coroutines is identified by suspend, but the real point of suspension is also determined by whether to return COROUTINE_SUSPENDED, while code embodiment handles the suspension and recovery of coroutines through the state machine. When it is necessary to suspend, leave the scene and set the next state point, and then suspend the coroutine by exiting the method. The current thread is not blocked during suspension. The corresponding recovery is performed by resumeWith to enter the next state of the state machine, while resuming the previously suspended scene when entering the next state.
This paper mainly introduces the principle of coroutine suspension and recovery, and also analyzes the execution process of coroutine state machine. Hope to learn coroutine partners can be helpful, please look forward to the subsequent coroutine analysis.
project
Android_startup: provides a simple and efficient way to initialize components during application startup, optimizing startup speed. Not only does it support all the features of Jetpack App Startup, but it also provides additional synchronous and asynchronous waiting, thread control, and multi-process support.
AwesomeGithub: Based on Github client, pure exercise project, support componentized development, support account password and authentication login. Kotlin language for development, the project architecture is based on Jetpack&DataBinding MVVM; Popular open source technologies such as Arouter, Retrofit, Coroutine, Glide, Dagger and Hilt are used in the project.
Flutter_github: a cross-platform Github client based on Flutter, corresponding to AwesomeGithub.
Android-api-analysis: A comprehensive analysis of Knowledge points related to Android with detailed Demo to help readers quickly grasp and understand the main points explained.
Daily_algorithm: an algorithm of the day, from shallow to deep, welcome to join us.