Foreword: Only those who bravely climb the rough path can hope to reach the zenith of glory. ——Marx
foreword
After studying the previous two coroutines, I believe that everyone is very familiar with the use of coroutines. In line with the mentality of knowing why it is more necessary to know why it is, I really want to know how it can synchronize asynchronous code? How does the coroutine implement thread scheduling? What is the nature of the suspend and resume of coroutines? Here today I will answer them all for you.
The whole Kotlin coroutine learning is divided into three parts, this article is the third one:
Advanced Kotlin Coroutine Combat (1. Foundation Building)
(This article requires the knowledge points of the coroutines in the previous two articles as a basis)
Outline of this article
The core of the coroutine is suspend and resume , but these two names confuse us to a certain extent, because these two terms do not allow us to have a clear understanding of the source code and its implementation principles.
The suspension of the coroutine is essentially the suspension of the method, and the suspension of the method is essentially the recovery of the coroutine, which is essentially the return
recovery of the method, and the essence of the recovery is callback
the callback.
return
But we can't see and callback
callback in the Kotlin coroutine source code . In fact, these are done by the kotlin compiler for us. You can't see why just by looking at the kotlin source code. You need to decompile it into a Java file to see the essence place.
Tools
Through -> kotlin
->show kotlin ByteCode
in the toolbar of AS , the obtained java bytecode needs to be Decompile
decompiled into java source code by clicking the button:
1. The main structure of the coroutine
1.suspend fun
Let's review the suspend function again:
suspend
It is the core keyword of Kotlin coroutine;suspend
Functions decorated with keywords are called ,挂起函数
and挂起函数
can only be called within the body of the coroutine or other挂起函数
;- During the compilation phase of the method modified by keywords
suspend
, the compiler will modify the signature of the method, including the return value, modifiers, input parameters, and method body implementation.
@GET("users/{login}")
suspend fun getUserSuspend(@Path("login") login: String): User
Decompile the above suspend function:
@GET("users/{login}")
@Nullable
Object getUserSuspend(@Path("login") @NotNull String var1, @NotNull Continuation var2);
- After decompiling, you will find that there is one more
Continuation
parameter (it iscallback
), that is to say, you need to pass one when calling the suspend functionContinuation
, but this parameter is passed quietly by the compiler, not by us. This is why the suspending function can only be executed in a coroutine or other suspending functions, because only in suspending functions or coroutinesContinuation
. - But how does the compiler determine which methods require
callback
? It issuspend
distinguished by keywords. The modified method will be specially processed by the Kotlin compilersuspend
during compilation . The compiler will think that once a method is added with the keyword, it may cause the coroutine to suspend execution, so it will pass the request to the method at this time . After the execution of the method is completed, go back through the callback, so that the coroutine can resume and continue to execute.suspend
Continuation
Continuation
- It also changed the return value
User
toObject
.
2.Continuation
Continuation
is a very important concept in Kotlin coroutines, it represents一个挂起点之后的延续操作
.
//Continuation接口表示挂起点之后的延续,该挂起点返回类型为“T”的值。
public interface Continuation<in T> {
//对应这个Continuation的协程上下文
public val context: CoroutineContext
//恢复相应协程的执行,传递一个成功或失败的结果作为最后一个挂起点的返回值。
public fun resumeWith(result: Result<T>)
}
//将[value]作为最后一个挂起点的返回值,恢复相应协程的执行。
fun <T> Continuation<T>.resume(value: T): Unit =
resumeWith(Result.success(value))
//恢复相应协程的执行,以便在最后一个挂起点之后重新抛出[异常]。
fun <T> Continuation<T>.resumeWithException(exception: Throwable): Unit =
resumeWith(Result.failure(exception))
Continuation
There is a resumeWith
function that accepts a parameter of type Result. When the result is successfully obtained, call resumeWith(Result.success(value))
or call the extension function resume(value)
; when an exception occurs, call resumeWith(Result.failure(exception))
or call the extension function resumeWithException(exception)
, which is Continuation
the recovery call of .
Continuation
Similar to the network request callback Callback
, it is also a callback for request success or failure:
public interface Callback {
//请求失败回调
void onFailure(Call call, IOException e);
//请求成功回调
void onResponse(Call call, Response response) throws IOException;
}
3.SuspendLambda
suspend{}
In fact, it is the body of the coroutine . It is the logic that the coroutine actually executes. SuspendLambda
It will create a class, which is Continuation
the implementation class.
2. The creation process of the coroutine
1. Creation of coroutines
The most primitive api for creating coroutines provided by the standard library:
public fun <T> (suspend () -> T).createCoroutine(
completion: Continuation<T>
): Continuation<Unit>
public fun <R, T> (suspend R.() -> T).createCoroutine(
receiver: R,
completion: Continuation<T>
): Continuation<Unit>
After the coroutine is created, there will be two Contiunation
, which need to be clearly distinguished:
completion
: Indicates the body of the coroutine. After the coroutine is executed, anContinuation
instance needs to be called when it resumes ;Contiunation<Unit>
: It is the carrier of the created coroutine , and the function will be passed to this instance as the actual execution body(suspend () -> T)
of the coroutine .
These two Contiunation
are different things. What is passed in completion
is actually the body of the coroutine. After the coroutine is executed, it needs a Contiunation
callback to execute, so it is called completion
; there is another one returned Contiunation
, which is the carrier created by the coroutine. After all the resumes in it are executed completion
The above resumeWith()
method will be called to resume the coroutine.
2. The scope of the coroutine
Simulate the creation of network requests in the Activity of Androidx, and use this example to dig into the principle of coroutines:
lifecycleScope.launch(Dispatchers.IO) {
val result = requestUserInfo()
tvName.text = result
}
/**
* 模拟请求,2秒后返回数据
*/
suspend fun requestUserInfo(): String {
delay(2000)
return "result form userInfo"
}
Follow up lifecycleScope
the source code:
val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
get() = lifecycle.coroutineScope
val Lifecycle.coroutineScope: LifecycleCoroutineScope
get() {
while (true) {
······
//关联声明周期的作用域实现类
val newScope = LifecycleCoroutineScopeImpl(
this,
SupervisorJob() + Dispatchers.Main.immediate
)
//注册生命周期
newScope.register()
return newScope
}
}
The scope is actually the scope defined for the coroutine . To ensure that all coroutines will be tracked, Kotlin does not allow CoroutineScope
new coroutines to be started without using them.
lifecycleScope is created by lifecycle
, SupervisorJob()
, which is a scope associated with the host lifecycle. bound to this . When the host is destroyed, this scope is also canceled.Dispatchers.Main
LifecycleCoroutineScopeImpl
CoroutineScope
LifecycleOwner
Lifecycle
3. The start of the coroutine
Enter launch()
:
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
//创建新的上下文
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
//延迟执行的协程
LazyStandaloneCoroutine(newContext, block) else
//独立的协程
StandaloneCoroutine(newContext, active = true)
//启动协程
coroutine.start(start, coroutine, block)
//返回coroutine,coroutine中实现了job接口
return coroutine
}
The parameter block: suspend CoroutineSope.() -> Unit
represents the coroutine code, which is actually a closure code block.
Three things are done here:
- Create a new coroutine context according to the context parameter
CoroutineContext
; - Create , create
Coroutine
if the startup mode is , otherwise create ;Lazy
LazyStandaloneCoroutine
StandaloneCoroutine
coroutine.start()
Start the coroutine.
newCoroutineContext
public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
//将作用域的上下文与传入的参数合并为新的上下文
val combined = coroutineContext + context
val debug = if (DEBUG) combined + CoroutineId(COROUTINE_ID.incrementAndGet()) else combined
return if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null)
debug + Dispatchers.Default else debug
}
Create a context for a new coroutine. By +
merging the scoped context coroutineContext
with the passed-in context context
into a new context. ContinuationInterceptor
It is used by default when no other scheduler or is specified Dispatchers.Default
.
StandaloneCoroutine
private open class StandaloneCoroutine(
parentContext: CoroutineContext,
active: Boolean
) : AbstractCoroutine<Unit>(parentContext, active) {
override fun handleJobException(exception: Throwable): Boolean {
//处理异常
handleCoroutineException(context, exception)
return true
}
}
public abstract class AbstractCoroutine<in T>(
protected val parentContext: CoroutineContext,
active: Boolean = true
) : JobSupport(active), Job, Continuation<T>, CoroutineScope {
······
//启动协程
public fun <R> start(start: CoroutineStart, receiver: R, block: suspend R.() -> T) {
initParentJob()
//根据[start]参数启动协程
start(block, receiver, this)
}
}
If you do not specify the startup mode, it will be used by default CoroutineStart.DEFAULT
, creating an independent coroutine StandaloneCoroutine
, and
StandaloneCoroutine
inheriting AbstractCoroutine
the class, and rewriting handleJobException()
the method of the parent class. AbstractCoroutine
Abstract base class for implementing coroutines in coroutine builders,
implementing interfaces such as Continuation
, Job
and . So is itself one .CoroutineScope
AbstractCoroutine
Continuation
coroutine.start()
start(block, receiver, this)
From the source code above, the coroutine start coroutine.start()
method is AbstractCoroutine
implemented in the class, which involves operator overloading , and then the method will actually call CoroutineStart#invoke()
the method, and pass the code block, receiver, and completion
other parameters CoroutineStart
to .
public enum class CoroutineStart {
//···
fun <R, T> invoke(block: suspend R.() -> T, receiver: R, completion: Continuation<T>): Unit =
when (this) {
DEFAULT -> block.startCoroutineCancellable(receiver, completion)
ATOMIC -> block.startCoroutine(receiver, completion)
UNDISPATCHED -> block.startCoroutineUndispatched(receiver, completion)
LAZY -> Unit
}
}
Use this coroutine strategy to [block]
start the corresponding block as a coroutine. Here [block]
is the code block executed in the coroutine.
- block : the code block executed in the coroutine;
- receiver : receiver;
- completion : The body of the coroutine. After the coroutine is executed, an
Continuation
instance needs to be called when it is resumed .
Above AbstractCoroutine
we see completion
that passing is this
, that is, AbstractCoroutine
itself, that is, Coroutine
the coroutine itself. So this completion
is the coroutine body. (This is Continuation
the first layer of the three-layer packaging)
Then enter startCoroutineCancellable()
, you can start the coroutine in a cancelable way, so that you can cancel the coroutine while waiting for scheduling:
internal fun <R, T> (suspend (R) -> T).startCoroutineCancellable(receiver: R, completion: Continuation<T>) =
runSafely(completion) {
// 1.创建一个没有被拦截的 Continuation
createCoroutineUnintercepted(receiver, completion)
// 2.添加拦截器
.intercepted()
// 3.执行协程,也是调用continuation.resumeWith(result)
.resumeCancellableWith(Result.success(Unit))
}
There are three main things done here:
- Create a new one
Continuation
. - Adding an interceptor to is also the key to thread scheduling
Continuation
.ContinuationInterceptor
resumeCancellableWith
The final callcontinuation.resumeWith(result)
executes the coroutine.
4. CreateContinuation<Unit>
createCoroutineUnintercepted()
Each time this function is called, a new instance of the suspendable computation is created. Start executing the created coroutine
by calling on the returned Continuation<Unit>
instance .resumeWith(Unit)
#IntrinsicsJvm.kt
public actual fun <R, T> (suspend R.() -> T).createCoroutineUnintercepted(
receiver: R,
completion: Continuation<T>
): Continuation<Unit> {
//返回Continuation<Unit>,它就是协程的载体
val probeCompletion = probeCoroutineCreated(completion)
return if (this is BaseContinuationImpl)
//如果调用者是 `BaseContinuationImpl` 或者其子类
create(receiver, probeCompletion)
else {
createCoroutineFromSuspendFunction(probeCompletion) {
(this as Function2<R, Continuation<T>, Any?>).invoke(receiver, it)
}
}
}
Key point : Create and return an unintercepted Continuation, which is the carrier of the coroutine . The function will be passed to this instance as the actual execution body(suspend () -> T)
of the coroutine . This encapsulates the code running logic and recovery interface of the coroutine, which will be mentioned below.Continuation
Because this is (suspend () -> T)
and SuspendLambda
is BaseContinuationImpl
the implementation class of , execute create()
the method to create a coroutine carrier:
abstract class BaseContinuationImpl {
//···
public open fun create(completion: Continuation<*>): Continuation<Unit> {
throw UnsupportedOperationException("create(Continuation) has not been overridden")
}
public open fun create(value: Any?, completion: Continuation<*>): Continuation<Unit> {
throw UnsupportedOperationException("create(Any?;Continuation) has not been overridden")
}
//···
}
create()
is BaseContinuationImpl
a public method in the class. So who implemented this method? Look at the explanation of the relationship between SuspendLambda
and BaseContinuationImpl
and Continuation
.
5.SuspendLambda and its parent class
suspend{}
As mentioned above (suspend R.() -> T)
, it is the logic that the coroutine really needs to execute. The incoming lambda expression is compiled into a SuspendLambda
subclass inherited from , SuspendLambda
which is Continuation
the implementation class of .
internal abstract class SuspendLambda(
public override val arity: Int,
completion: Continuation<Any?>?
) : ContinuationImpl(completion), FunctionBase<Any?>, SuspendFunction {
constructor(arity: Int) : this(arity, null)
public override fun toString(): String =
if (completion == null)
Reflection.renderLambdaToString(this) //这是 lambda
else
super.toString() //这是 continuation
}
while SuspendLambda
inherits from ContinuationImpl
:
internal abstract class ContinuationImpl(
completion: Continuation<Any?>?,
) : BaseContinuationImpl(completion) {
private var intercepted: Continuation<Any?>? = null
public fun intercepted(): Continuation<Any?> =
intercepted
?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
.also {
intercepted = it }
protected override fun releaseIntercepted() {
··· }
}
ContinuationImpl
Also inherited from BaseContinuationImpl
, the concrete implementation of the method is SuspendLambda
the method of :resume()
BaseContinuationImpl
resumeWith()
internal abstract class BaseContinuationImpl(
//每个BaseContinuationImpl实例都会引用一个完成Continuation,用来在当前状态机流转结束时恢复这个Continuation
public val completion: Continuation<Any?>?
) : Continuation<Any?>, CoroutineStackFrame, Serializable {
//resumeWith() 中通过循环由里到外恢复Continuation
public final override fun resumeWith(result: Result<Any?>) {
var current = this
var param = result
while (true) {
probeCoroutineResumed(current)
with(current) {
val completion = completion!! //completion为null则会抛出异常
val outcome: Result<Any?> =
try {
// 1.调用 invokeSuspend 方法执行,执行协程的真正运算逻辑
val outcome = invokeSuspend(param)
// 2.如果已经挂起则提前结束
if (outcome === COROUTINE_SUSPENDED) return
Result.success(outcome)
} catch (exception: Throwable) {
Result.failure(exception)
}
//当invokeSuspend方法没有返回COROUTINE_SUSPENDED,那么当前状态机流转结束,即当前suspend方法执行完毕,释放拦截
releaseIntercepted()
if (completion is BaseContinuationImpl) {
//3.如果 completion 是 BaseContinuationImpl,内部还有suspend方法,则会进入循环递归
current = completion
param = outcome
} else {
//4.否则是最顶层的completion,则会调用resumeWith恢复上一层并且return
// 这里实际调用的是其父类 AbstractCoroutine 的 resumeWith 方法
completion.resumeWith(outcome)
return
}
}
}
}
//·····
}
There are four main things done here:
- Call
invokeSuspend
the method, execute the real operation logic of the coroutine, and return a result; - If
outcome
yesCOROUTINE_SUSPENDED
, the suspend method is executed in the code block, then continue to suspend; - If
completion
yesBaseContinuationImpl
, there is a suspend method inside, it will enter the loop recursion, and continue to execute the suspension; - If
completion
not , the method ofBaseContinuationImpl
the parent class is actually called .AbstractCoroutine
resumeWith
Next, let's look at the implementation AbstractCoroutine
of resumeWith
:
public abstract class AbstractCoroutine<in T>(
protected val parentContext: CoroutineContext,
active: Boolean = true
) : JobSupport(active), Job, Continuation<T>, CoroutineScope {
//以指定的结果完成执行协程
public final override fun resumeWith(result: Result<T>) {
// 1. 获取当前协程的技术状态
val state = makeCompletingOnce(result.toState())
// 2. 如果当前还在等待完成,说明还有子协程没有结束
if (state === COMPLETING_WAITING_CHILDREN) return
// 3. 执行结束恢复的方法,默认为空
afterResume(state)
}
//···
}
One of them completion
is BaseContinuationImpl
that each instance represents a suspend method state machine. resumeWith()
It encapsulates the operation logic of the coroutine and is used to start and resume the coroutine; while the other type is completion
mainly AbstractCoroutine
responsible for maintaining the state and management of the coroutine, and it resumeWith
is to complete the coroutine and resume the caller's coroutine.
Its inheritance relationship is: SuspendLambda -> ContinuationImpl -> BaseContinuationImpl -> Continuation
.
So create()
the method creates Continuation
is a SuspendLambda
object.
Back to the method above createCoroutineUnintercepted()
:
//IntrinsicsJvm.kt
public actual fun <R, T> (suspend R.() -> T).createCoroutineUnintercepted(
receiver: R,
completion: Continuation<T>
): Continuation<Unit> {
//返回Continuation<Unit>,它就是协程的载体
val probeCompletion = probeCoroutineCreated(completion)
return if (this is BaseContinuationImpl)
//如果调用者是 `BaseContinuationImpl` 或者其子类
create(receiver, probeCompletion)
else {
createCoroutineFromSuspendFunction(probeCompletion) {
(this as Function2<R, Continuation<T>, Any?>).invoke(receiver, it)
}
}
}
In fact, this code is found in the JVM platform, in IntrinsicsJvm.kt
the class, but it looks like this in the Android source code:
fun <R, T> (suspend R.() -> T).createCoroutineUnintercepted(
receiver: R,
completion: Continuation<T>
): Continuation<kotlin.Unit> {
/* compiled code */ }
compiled code
It has been prompted that the code inside is compiled code.
As mentioned earlier, the complete coroutine principle cannot be seen in the coroutine source code. Some codes are processed by the kotlin compiler, so when studying the running process of the coroutine, you can’t see it just by looking at the source code of kotlin. Essentially, it needs to be decompiled into a Java file to see how the decompiled code has been modified.
6. Function
Creation of
Decompile the code of the coroutine simulating network request:
fun getData() {
lifecycleScope.launch {
val result = requestUserInfo()
tvName.text = result
}
}
The decompiled code is as follows (the code has been deleted), you will find that a huge change has taken place, and these tasks are done by the kotlin compiler for us:
public final void getData() {
BuildersKt.launch$default((CoroutineScope)LifecycleOwnerKt.getLifecycleScope(this), (CoroutineContext)null, (CoroutineStart)null, (Function2)(new Function2((Continuation)null) {
int label; // 初始值为0
@Nullable
public final Object invokeSuspend(@NotNull Object $result) {
//···
Object var10000 = requestUserInfo(this); //执行挂起函数
//···
String result = (String)var10000;
CoroutinesActivity.this.getTvName().setText((CharSequence)result);
return Unit.INSTANCE;
}
@NotNull
public final Continuation create(@Nullable Object value, @NotNull Continuation completion) {
Intrinsics.checkParameterIsNotNull(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);
}
lifecycleScope.launch {}
After decompilation, parameters such as , , , 3, etc. were added , CoroutineScope
all CoroutineContext
of which were done by the kotlin compiler for us. This is where the top-level completion handles the suspension and recovery of coroutines. Once it is restored here, it means that the entire coroutine has been restored.CoroutineStart
Function2
Object
One is created here Function2
, and there are three important methods in it:
- invokeSuspend(result) :
luanch{}
The code inside is executed, so it executes the real operation logic of the coroutine; - create(value, completion)
completion
: Create a and return through the passed parametersFunction2
, actually oneContinuation
; - invoke(var1, var2) : Rewrite
Funtion.invoke()
the method, and call it in a chain through the passed parameterscreate().invokeSuspend()
.
-
From the above, we know that
(suspend R.() -> T)
it isBaseContinuationImpl
the implementation class of , so we will useonCreate()
the method to create , and create a new oneContinuation
through the parameter , as a return, this is the created coroutine carrier ; (this is the second layer of packaging of the three-layer packaging)completion
Function2
Continuation
Continuation
-
Then call to
resumeWith()
start the coroutine, then the method of will be executed, and the method will be executed at this timeBaseContinuationImpl
to execute the real operation logic of the coroutine .resumeWith()
invokeSuspend()
The process of creating a coroutine is as follows:
3. Suspension and recovery of coroutines
The core of coroutine work is its internal state machine and invokeSuspend()
function. requestUserInfo()
The method is a suspending function. Here, the principle of the coroutine state machine is explained by decompiling it, and the suspension and recovery of the coroutine are reversely analyzed.
1. Method suspension
//延时2000毫秒,返回一个String结果
suspend fun requestUserInfo(): String {
delay(2000)
return "result form userInfo"
}
The decompiled code is as follows (the code has been deleted), and it is also found that a huge change has taken place, and these tasks are done by the kotlin compiler for us:
//1.函数返回值由String变成Object,入参也增加了Continuation参数
public final Object requestUserInfo(@NotNull Continuation completion) {
//2.通过completion创建一个ContinuationImpl,并且复写了invokeSuspend()
Object continuation = new ContinuationImpl(completion) {
Object result;
int label; //初始值为0
@Nullable
public final Object invokeSuspend(@NotNull Object $result) {
this.result = $result;
this.label |= Integer.MIN_VALUE;
return requestUserInfo(this);//又调用了requestUserInfo()方法
}
};
Object $result = (continuation).result;
Object var4 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
//状态机
//3.方法被恢复的时候又会走到这里,第一次进入case 0分支,label的值从0变为1,第二次进入就会走case 1分支
switch(continuation.label) {
case 0:
ResultKt.throwOnFailure($result);
continuation.label = 1;
//4.delay()方法被suspend修饰,传入一个continuation回调,返回一个object结果。这个结果要么是`COROUTINE_SUSPENDED`,否则就是真实结果。
Object delay = DelayKt.delay(2000L, continuation)
if (delay == var4) {
//如果是COROUTINE_SUSPENDED则直接return,就不会往下执行了,requestUserInfo()被暂停了。
return var4;
}
break;
case 1:
ResultKt.throwOnFailure($result);
break;
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
return "result form userInfo";
}
The main steps above are:
- The return value of the function
String
is changed from toObject
, and the parameter is added after compiling the function without input parametersContinuation
. What we originally needed to docallback
, now the compiler does it for us. - According to
completion
create oneContinuationImpl
, overwriteinvokeSuspend()
the method, in this method it callsrequestUserInfo()
the method again, here it calls itself again (is it amazing), and passescontinuation
in. - In the switch statement, the default initial value of is 0, and it will enter the branch
label
for the first time , which is a suspending function. When the above parameter is passed in, there will be a return value of type. This result is either true or not.case 0
delay()
continuation
Object
COROUTINE_SUSPENDED
DelayKt.delay(2000, continuation)
If the return result is yesCOROUTINE_SUSPENDED
, return directly, then the execution of the method is ended, and the method is suspended.
This is the real principle of hanging . So even if the function is suspend
modified, it may not hang. It is necessary for the code inside to have a return value of COROUTINE_SUSPENDED
such a flag after compilation, so case 0
when the program is executed, it will return. That means the method is suspended, so the coroutine is also suspended. So the suspension of the association is actually the suspension of the method, and the essence of the suspension of the method is return.
2.COROUTINE_SUSPENDED
Object var4 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
//执行已暂停,并且不会立即返回任何结果
public val COROUTINE_SUSPENDED: Any get() = CoroutineSingletons.COROUTINE_SUSPENDED
internal enum class CoroutineSingletons {
COROUTINE_SUSPENDED, UNDECIDED, RESUMED }
In var4 = IntrinsicsKt.getCOROUTINE_SUSPENDED()
, COROUTINE_SUSPENDED
it is an enumeration constant, indicating that the coroutine has been suspended and will not return any results immediately. Then DelayKt.delay()
the return value is COROUTINE_SUSPENDED
returned.
Follow up DelayKT
to see COROUTINE_SUSPENDED
how is obtained:
find DelayKT
the class (note: no Delay.kt
, don’t make a mistake), Decomplie to java
and decompile it into java source code:
public final class DelayKt {
//增加了Object返回值,并且追加了一个Continuation参数,
@Nullable
public static final Object delay(long timeMillis, @NotNull Continuation $completion) {
if (timeMillis <= 0L) {
return Unit.INSTANCE;
} else {
//···
getDelay(cont.getContext()).scheduleResumeAfterDelay(timeMillis, cont);
Object var10000 = cancellable$iv.getResult();
//···
return var10000;
}
}
}
It can be seen that the return value DelayKt.delay()
has been added , and a parameter has Object
been added . This return value is obtained in :completion
var10000
cancellable$iv.getResult()
@PublishedApi
internal fun getResult(): Any? {
setupCancellation()
//尝试挂起,如果返回TRUE则返回COROUTINE_SUSPENDED
if (trySuspend()) return COROUTINE_SUSPENDED
//···
return getSuccessfulResult(state)
}
trySuspend()
Try to suspend the method and return if it returns true COROUTINE_SUSPENDED
:
private val _decision = atomic(UNDECIDED)
private fun trySuspend(): Boolean {
//循环遍历里面的值,而_decision初始值为UNDECIDED,那么第一次肯定返回true
_decision.loop {
decision ->
when (decision) {
//返回true,并且把当前状态更改为SUSPENDED,代表它以经被挂起了
UNDECIDED -> if (this._decision.compareAndSet(UNDECIDED, SUSPENDED)) return true
RESUMED -> return false
else -> error("Already suspended")
}
}
}
trySuspend()
It loops through _decision
the value of , _decision
the initial value is UNDECIDED
, then it will enter the branch for the first time UNDECIDED -> if (this._decision.compareAndSet(UNDECIDED, SUSPENDED)) return true
, and return true here, and change the current state to SUSPENDED
, which means it has been suspended.
//方法的状态只有在调用tryResume时才会把状态更改为RESUMED
private fun tryResume(): Boolean {
_decision.loop {
decision ->
when (decision) {
//返回true,并且把状态改为RESUMED
UNDECIDED -> if (this._decision.compareAndSet(UNDECIDED, RESUMED)) return true
SUSPENDED -> return false
else -> error("Already resumed")
}
}
}
The state of this method will be called when it restores tryResume()
the state to RESUMED
. This is the decision state machine :
Then trySuspend()
return true
returns the enumeration constant, then returns , so the following judgment conditions will be satisfied, and it will return directly getResult()
. method is a true suspending function that causes the coroutine to be suspended.COROUTINE_SUSPENDED
DelayKt.delay()
COROUTINE_SUSPENDED
delay()
So requestUserInfo()
the method is delay(2000)
suspended in and called in the coroutine, then the coroutine is also suspended, and the subsequent results result form userInfo
are not returned. So this means that suspend
the modified function does not necessarily cause the coroutine to be suspended, and the implementation inside needs to have a return value after compilation and be COROUTINE_SUSPENDED
.
3. Method recovery
Continue to return to requestUserInfo()
the principle of analysis and recovery:
@Nullable
public final Object requestUserInfo(@NotNull Continuation completion) {
//···
Object continuation = new ContinuationImpl(completion) {
Object result;
int label; //初始值为0
@Nullable
public final Object invokeSuspend(@NotNull Object $result) {
this.result = $result;
this.label |= Integer.MIN_VALUE;
return requestUserInfo(this);//又调用了requestUserInfo()方法
}
};
Object $result = (continuation).result;
Object var4 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
//方法被恢复的时候又会走到这里,第一次进入case 0,第二次 ontinuation.label = 1,所以能继续执行下去返回结果
switch(continuation.label) {
case 0:
ResultKt.throwOnFailure($result);
continuation.label = 1;
//delay()方法被suspend修饰,传入一个continuation回调,返回一个object结果。这个结果要么是`COROUTINE_SUSPENDED`,否则就是真实结果。
Object delay = DelayKt.delay(2000L, continuation)
if (delay == var4) {
//如果是COROUTINE_SUSPENDED则直接return,就不会往下执行了。
return var4;
}
break;
case 1:
ResultKt.throwOnFailure($result);
break;
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
return "result form userInfo";
}
- Since
delay()
isio
an operation, after 2000 mm it will becontinuation
returned via the callback passed to it. - Calling back to the method
ContinuationImpl
in this classresumeWith()
will callinvokeSuspend()
the method again, and then callrequestUserInfo()
the method again. - It will enter the switch statement again. Since it
case 0
is assignedlabel = 1
a value of 1 at for the first time, it will entercase 1
the branch this time and return the resultresult form userInfo
. - And
requestUserInfo()
the return value of isinvokeSuspend()
returned as the return value of . When it is re-executed, it means that the method is restored .
So invokeSuspend()
how is the method triggered by the callback? What is the use of it to get the return value?
It is mentioned above that ContinuationImpl
inherits from BaseContinuationImpl
, and it implements continuation
the interface and overrides resumeWith()
the method, which calls val outcome = invokeSuspend(param)
the method. (The source code has been deleted)
internal abstract class BaseContinuationImpl(
public val completion: Continuation<Any?>?
) : Continuation<Any?>, CoroutineStackFrame, Serializable {
//这个实现是最终的,用于展开 resumeWith 递归。
public final override fun resumeWith(result: Result<Any?>) {
var current = this
var param = result
while (true) {
with(current) {
val completion = completion!!
val outcome: Result<Any?> =
try {
// 1.调用 invokeSuspend()方法执行,执行协程的真正运算逻辑,拿到返回值
val outcome = invokeSuspend(param)
// 2.如果返回的还是COROUTINE_SUSPENDED则提前结束
if (outcome == COROUTINE_SUSPENDED) return
Result.success(outcome)
} catch (exception: Throwable) {
Result.failure(exception)
}
if (completion is BaseContinuationImpl) {
//3.如果 completion 是 BaseContinuationImpl,内部还有suspend方法,则会进入循环递归,继续执行和恢复
current = completion
param = outcome
} else {
//4.否则是最顶层的completion,则会调用resumeWith恢复上一层并且return
// 这里实际调用的是其父类 AbstractCoroutine 的 resumeWith 方法
completion.resumeWith(outcome)
return
}
}
}
}
In fact, any suspending function will be called into the method when it BaseContinuationImpl
resumes resumeWith()
.
-
Once
invokeSuspend()
the method is executed, thenrequestUserInfo()
will be called again, and the return value ofinvokeSuspend()
will be obtained , in which to judge the operation after our method resumes according to the return value of .requestUserInfo()
ContinuationImpl
val outcome = invokeSuspend()
requestUserInfo()
-
If
outcome
it isCOROUTINE_SUSPENDED
a constant, it means that even if you were resumed and executed,if (outcome == COROUTINE_SUSPENDED) return
you were immediately suspended again, so you returned again. -
If this recovery
outcome
is a normal result, it will come to the conclusioncompletion.resumeWith(outcome)
that the currently suspended method has been executed, and the methodAbstractCoroutine
of its parent class is actually calledresumeWith
, then the coroutine will resume.
We know requestUserInfo()
that must be called by the coroutine (from the decompiled code above, we know that a Continuation completion
parameter will be passed), and requestUserInfo()
the coroutine will be completion.resumeWith()
restored after the method is restored , so the restoration of the coroutine is essentially the restoration of the method.
This is the process of analyzing the suspension and recovery of coroutines by decompiling the kotlin source code in android studio. The flow chart is as follows:
4. Suspend and resume running in coroutines
So requestUserInfo()
what is the whole suspending and resuming process executed by the method in the coroutine?
fun getData() {
lifecycleScope.launch {
val result = requestUserInfo()
tvName.text = result
}
}
//模拟请求,2秒后返回数据
suspend fun requestUserInfo(): String {
delay(2000)
return "result form userInfo"
}
Decompiled code:
public final void getData() {
BuildersKt.launch$default((CoroutineScope)LifecycleOwnerKt.getLifecycleScope(this), (CoroutineContext)null, (CoroutineStart)null, (Function2)(new Function2((Continuation)null) {
int label; // 初始值为0
@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; //修改label
var10000 = requestUserInfo(this); //执行挂起函数
if (var10000 == var3) {
return var3; //如果var10000是COROUTINE_SUSPENDED则直接挂起协程
}
break;
case 1:
ResultKt.throwOnFailure($result);
var10000 = $result; //返回实际结果
break;
//···
}
String result = (String)var10000;
CoroutinesActivity.this.getTvName().setText((CharSequence)result);
return Unit.INSTANCE;
}
@NotNull
public final Continuation create(@Nullable Object value, @NotNull Continuation completion) {
Intrinsics.checkParameterIsNotNull(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 requestUserInfo(@NotNull Continuation completion) {
//···
Object continuation = new ContinuationImpl(completion) {
Object result;
int label; //初始值为0
@Nullable
public final Object invokeSuspend(@NotNull Object $result) {
this.result = $result;
this.label |= Integer.MIN_VALUE;
return requestUserInfo(this); //又调用了requestUserInfo()方法
}
};
Object $result = (continuation).result;
Object var4 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
//状态机
//方法被恢复的时候又会走到这里,第一次进入case 0,第二次 ontinuation.label = 1,所以就打印了日志输出
switch(label) {
case 0:
ResultKt.throwOnFailure($result);
continuation.label = 1;
//delay()方法被suspend修饰,传入一个continuation回调,返回一个object结果。这个结果要么是`COROUTINE_SUSPENDED`,否则就是真实结果。
Object delay = DelayKt.delay(2000L, continuation)
if (delay == var4) {
//如果是COROUTINE_SUSPENDED则直接return,就不会往下执行了,requestUserInfo()被暂停了。
return var4;
}
break;
case 1:
ResultKt.throwOnFailure($result);
break;
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
return "result form userInfo";
}
- You can see that the decompiled code in the coroutine
requestUserInfo()
is similar to the decompiled code of the method, and the methodFunction2
is also copied in itinvokeSuspend()
, and the state machine is also similar. - Determine whether the return value is
case 0
, and if so, suspend the coroutine. We know from the above analysis that the value returned for the first time is , so it is suspended, and the coroutine is also suspended. So the suspension of the coroutine is actually the suspension of the method.requestUserInfo()
COROUTINE_SUSPENDED
requestUserInfo()
COROUTINE_SUSPENDED
requestUserInfo()
- The principle of coroutine recovery is
requestUserInfo()
roughly the same as that of recovery. Passed inrequestUserInfo(this)
when calling .Continuation
- Then
requestUserInfo()
after 2000 milliseconds, the function will callinvokeSuspend()
back the result to the insidecompletion
of the upper layer when it resumesresumeWith()
, then the of the coroutineinvokeSuspend(result)
will be called back. - The code following the previous suspend logic flows through the state machine. At this point
lable = 1
entercase 1
is assigned tovar10000
and the rest of the code is executed. SorequestUserInfo()
after the method is restored, the coroutine that called it is also restored, so the restoration of the coroutine is essentially the restoration of the method.
4. Scheduling of coroutines
1. Coroutine interception
The thread scheduling of the coroutine is realized through the interceptor, back to the previous startCoroutineCancellable
:
internal fun <R, T> (suspend (R) -> T).startCoroutineCancellable(
receiver: R,
completion: Continuation<T>) =
runSafely(completion) {
// 创建一个没有被拦截的 Coroutine
createCoroutineUnintercepted(receiver, completion)
// 添加拦截器
.intercepted()
// 执行协程
.resumeCancellableWith(Result.success(Unit))
}
Take a look intercepted()
at the specific implementation of :
//使用[ContinuationInterceptor]拦截Continuation
public actual fun <T> Continuation<T>.intercepted(): Continuation<T> =
(this as? ContinuationImpl)?.intercepted() ?: this
The interceptor intercepts the coroutine body every time (resuming) execution of the coroutine body SuspendLambda
. interceptContinuation()
One is intercepted in the method Continuation<T>
and another is returned Continuation<T>
. After the interception, Continuation
some things can be done, such as thread switching and so on.
internal abstract class ContinuationImpl(
completion: Continuation<Any?>?,
private val _context: CoroutineContext?
) : BaseContinuationImpl(completion) {
@Transient
private var intercepted: Continuation<Any?>? = null // 拦截到的Continuation
public fun intercepted(): Continuation<Any?> =
intercepted
?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
.also {
intercepted = it }
//······
}
And interceptContinuation()
the implementation of the method is in CoroutineDispatcher
, which is the base class that all coroutine scheduler implementations extend:
public abstract class CoroutineDispatcher :
AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
//是否需要调度
public open fun isDispatchNeeded(context: CoroutineContext): Boolean = true
//将可运行块的执行分派到给定上下文中的另一个线程上
public abstract fun dispatch(context: CoroutineContext, block: Runnable)
//通过DispatchedContinuation返回包装原始continuation的 continuation
public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
DispatchedContinuation(this, continuation)
}
Note: [block]
is a Runnable
type.
If the coroutine scheduler is passed, the closure code block in the coroutine determines the running thread environment. CoroutineDispatcher
There are three important methods:
- isDispatchNeeded() : Whether the start of the coroutine needs to be distributed to other threads.
- dispatch() : Dispatch the execution of the runnable block to another thread in a given context, and subclasses implement specific scheduling.
- interceptContinuation : Intercept the coroutine body and package it into one
DispatchedContinuation
.
Intercept the coroutine body and package it into one DispatchedContinuation
. When it executes the task, it will needDispatch()
judge whether the coroutine startup needs to be distributed to other threads. If it returns true, then it will call the dispatch(runnable)
method of the subclass to complete If false is returned for the start-up work of the coroutine, it will be CoroutineDispatcher
executed immediately by the current thread.
2. Coroutine distribution
Continuation
Calling on an intercepted resume(Unit)
ensures that both execution of the coroutine and completion occur within ContinuationInterceptor
the calling context established by the . And the intercepted continuation
is DispatchedContinuation
wrapped in one layer: (this is Continuation
the third layer of packaging in the three layers of packaging)
internal class DispatchedContinuation<in T>(
@JvmField val dispatcher: CoroutineDispatcher,
@JvmField val continuation: Continuation<T>
) : DispatchedTask<T>(MODE_UNINITIALIZED), CoroutineStackFrame, Continuation<T> by continuation {
//·····
override val delegate: Continuation<T>
get() = this
inline fun resumeCancellable(value: T) {
// 是否需要线程调度
if (dispatcher.isDispatchNeeded(context)) {
// 将协程的运算分发到另一个线程
dispatcher.dispatch(context, this)
} else {
// 如果不需要调度,立即恢复在在当前线程执行协程运算
withCoroutineContext(this.context, countOrElement) {
continuation.resumeWith(result)
}
}
}
override fun resumeWith(result: Result<T>) {
// 是否需要线程调度
if (dispatcher.isDispatchNeeded(context)) {
// 将协程的运算分发到另一个线程
dispatcher.dispatch(context, this)
} else {
// 如果不需要调度,立即恢复在当前线程执行协程运算
continuation.resumeWith(result)
}
}
//·····
}
DispatchedContinuation
The start and resume of coroutines are intercepted, respectively resumeCancellable(Unit)
and rewritten resumeWith(Result)
.
When distribution is required, the method dispatcher
of is called dispatch(context, this)
, this is a DispatchedTask
:
internal abstract class DispatchedTask<in T>(
@JvmField public var resumeMode: Int
) : SchedulerTask() {
public final override fun run() {
//·····
withContinuationContext(continuation, delegate.countOrElement) {
//恢复协程执行 最终调用resumeWith
continuation.resume(getSuccessfulResult(state))
}
//·····
}
}
DispatchedTask
Actually one Runnable
.
DispatchedContinuation.continuation.resumeWith()
When thread scheduling is required, it will call to start the coroutine after scheduling , wherecontinuation
isSuspendLambda
the instance;- When thread scheduling is not required, call directly
continuation.resumeWith()
to start the coroutine directly.
Continuation
That is to say, add the interception operation to the created resumeWith()
and intercept the running operation of the coroutine:
Analyze the specific implementation of the four scheduling modes:
3.Dispatchers.Unconfined
public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined
internal object Unconfined : CoroutineDispatcher() {
//返回false, 不拦截协程
override fun isDispatchNeeded(context: CoroutineContext): Boolean = false
override fun dispatch(context: CoroutineContext, block: Runnable) {
//····
}
}
Dispatchers.Unconfined
: Correspondingly Unconfined
, the return in it isDispatchNeeded()
is false, not limited to the coroutine scheduler of any particular thread. Then its parent class ContinuationInterceptor
will not hand over the scheduling of this task to the subclass for execution, but the parent class will execute it immediately in the current thread.
4.Dispatchers.Main
Dispatchers.Main
Inherited from Implement the scheduler MainCoroutineDispatcher
via :MainDispatcherLoader.dispatcher
public actual object Dispatchers {
@JvmStatic
public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher
}
MainDispatcherLoader
Created via the factory pattern MainCoroutineDispatcher
:
internal object MainDispatcherLoader {
@JvmField
val dispatcher: MainCoroutineDispatcher = loadMainDispatcher()
private fun loadMainDispatcher(): MainCoroutineDispatcher {
//···
val factories = FastServiceLoader.loadMainDispatcherFactory()
//···
return factories.maxByOrNull {
it.loadPriority }?.tryCreateDispatcher(factories)
}
}
public fun tryCreateDispatcher(factories: List<MainDispatcherFactory>): MainCoroutineDispatcher =
try {
createDispatcher(factories)
} catch (cause: Throwable) {
//如果出现异常则创建一个MissingDispatcher
createMissingDispatcher(cause, hintOnError())
}
MainDispatcherFactory
is an interface, created by implementing a class Dispatcher
:
public interface MainDispatcherFactory {
//子类实现
public fun createDispatcher(allFactories: List<MainDispatcherFactory>): MainCoroutineDispatcher
}
internal class AndroidDispatcherFactory : MainDispatcherFactory {
//由AndroidDispatcherFactory创建HandlerContext,可以看到它是一个主线程调度器
override fun createDispatcher(allFactories: List<MainDispatcherFactory>) =
HandlerContext(Looper.getMainLooper().asHandler(async = true), "Main")
//···
}
We have seen keywords such as AndroidDispatcherFactory
, Looper.getMainLooper()
, Main
etc. There is no doubt that this is the main thread scheduler:
internal class HandlerContext private constructor(
private val handler: Handler,
private val name: String?,
private val invokeImmediately: Boolean
) : HandlerDispatcher(), Delay {
//···
override val immediate: HandlerContext = _immediate ?:
HandlerContext(handler, name, true).also {
_immediate = it }
//invokeImmediately默认为false, Looper.myLooper() != handler.looper判断当前线程looper
override fun isDispatchNeeded(context: CoroutineContext): Boolean {
return !invokeImmediately || Looper.myLooper() != handler.looper
}
override fun dispatch(context: CoroutineContext, block: Runnable) {
handler.post(block)
}
//···
}
The inheritance relationship among them:
HandlerContext
-> HandlerDispatcher
-> MainCoroutineDispatcher()
-> CoroutineDispatcher
.
It isDispatchNeeded()
returns true, and when the coroutine is started, it is HandlerContext
distributed, and the distribution work in it is handler.post(runnable)
completed by distributing to the main thread. When restoring, it is also Dispatchers.Main
restored through this scheduler. When the task is completed, HandlerDispatcher
the code in the coroutine will be switched to the main thread again.
5.Dispatchers.IO
public actual object Dispatchers {
@JvmStatic
public val IO: CoroutineDispatcher = DefaultScheduler.IO
}
DefaultScheduler
The default scheduler of the coroutine scheduler is a thread scheduler that executes blocking tasks. This scheduler Dispatcher.Default
shares threads with the scheduler.
internal object DefaultScheduler : ExperimentalCoroutineDispatcher() {
val IO: CoroutineDispatcher = LimitingDispatcher(
this,
systemProp(IO_PARALLELISM_PROPERTY_NAME, 64.coerceAtLeast(AVAILABLE_PROCESSORS)),
"Dispatchers.IO",
TASK_PROBABLY_BLOCKING
)
//···
}
private class LimitingDispatcher(
private val dispatcher: ExperimentalCoroutineDispatcher,
//···
) : ExecutorCoroutineDispatcher(), TaskContext, Executor {
override val executor: Executor
get() = this
override fun execute(command: Runnable) = dispatch(command, false)
override fun dispatch(context: CoroutineContext, block: Runnable) = dispatch(block, false)
private fun dispatch(block: Runnable, tailDispatch: Boolean) {
var taskToSchedule = block
while (true) {
//没有超过限制,立即分发任务
if (inFlight <= parallelism) {
dispatcher.dispatchWithContext(taskToSchedule, this, tailDispatch)
return
}
//任务超过限制,则加入等待队列
queue.add(taskToSchedule)
//···
}
}
}
dispatcher.dispatchWithContext()
Dispatching tasks immediately is ExperimentalCoroutineDispatcher
implemented by:
public open class ExperimentalCoroutineDispatcher(
private val corePoolSize: Int,
private val maxPoolSize: Int,
//···
) : ExecutorCoroutineDispatcher() {
override val executor: Executor
get() = coroutineScheduler
private var coroutineScheduler = createScheduler()
//分发
override fun dispatch(context: CoroutineContext, block: Runnable): Unit = coroutineScheduler.dispatch(block)
//···
internal fun dispatchWithContext(block: Runnable, context: TaskContext, tailDispatch: Boolean) {
coroutineScheduler.dispatch(block, context, tailDispatch)
}
//创建调度器
private fun createScheduler() = CoroutineScheduler(corePoolSize, maxPoolSize, idleWorkerKeepAliveNs, schedulerName)
//···
}
ExperimentalCoroutineDispatcher
Distribute tasks to coroutineScheduler.dispatch()
implementations.
CoroutineScheduler
It is a thread pool Executor.
//协程调度器的主要目标是在工作线程上分配调度的协程,包括 CPU 密集型任务和阻塞任务。
internal class CoroutineScheduler(
val corePoolSize: Int,
val maxPoolSize: Int,
//···
) : Executor, Closeable {
override fun execute(command: Runnable) = dispatch(command)
//调度可运行块的执行,并提示调度程序该块是否可以执行阻塞操作(IO、系统调用、锁定原语等)
fun dispatch(block: Runnable, taskContext: TaskContext = NonBlockingContext, tailDispatch: Boolean = false) {
//1.构建Task,Task实现了Runnable接口
val task = createTask(block, taskContext)
//2.取当前线程转为Worker对象,Worker是一个继承自Thread的类,循环执行任务
val currentWorker = currentWorker()
//3.添加任务到本地队列
val notAdded = currentWorker.submitToLocalQueue(task, tailDispatch)
if (notAdded != null) {
//4.notAdded不为null,则再将notAdded(Task)添加到全局队列中
if (!addToGlobalQueue(notAdded)) {
throw RejectedExecutionException("$schedulerName was terminated")
}
}
if (task.mode == TASK_NON_BLOCKING) {
if (skipUnpark) return
//5.创建Worker并开始执行该线程
signalCpuWork()
} else {
// Increment blocking tasks anyway
signalBlockingWork(skipUnpark = skipUnpark)
}
}
}
The above code mainly does the following things:
- First of all, by
Runnable
building oneTask
, thisTask
actually implementsRunnable
the interface; - Take out the current thread and convert it to
Worker
, thisWorker
isThread
a class inherited from; - will
task
be submitted to the local queue; - If
task
the process of submitting to the local queue is unsuccessful, it will be added to the global queue; - Create
Worker
a thread and start executing tasks.
class Worker private constructor() : Thread() {
//woeker队列
val localQueue: WorkQueue = WorkQueue()
override fun run() = runWorker()
private fun runWorker() {
var rescanned = false
while (!isTerminated && state != WorkerState.TERMINATED) {
val task = findTask(mayHaveLocalTasks)
//找到任务,执行任务,重复循环
if (task != null) {
executeTask(task)
continue
} else {
mayHaveLocalTasks = false
}
}
}
private fun executeTask(task: Task) {
val taskMode = task.mode
idleReset(taskMode)
beforeTask(taskMode)
runSafely(task)
afterTask(taskMode)
}
}
//执行任务
fun runSafely(task: Task) {
try {
task.run()
}
//···
}
The run method is directly called runWorker()
, and inside is a while loop, which is continuously fetched from the queue for Task
execution and called task.run()
.
- Take it from the local queue or the global queue
Task
. - To execute this task, in the end, it is actually calling this
Runnable
run method.
That is to say, in Worker
this thread, the run method of this is executed Runnable
. Do you remember Runnable
who this is? It is what we have seen above DispatchedTask
. The run method here executes the coroutine task. Then we should look for the specific implementation logic of the run method DispatchedTask
.
internal abstract class DispatchedTask<in T>(
@JvmField public var resumeMode: Int
) : SchedulerTask() {
public final override fun run() {
//·····
withContinuationContext(continuation, delegate.countOrElement) {
//恢复协程执行,最终调用resumeWith
continuation.resume(getSuccessfulResult(state))
}
//·····
}
}
The run method executes continuation.resume
and resumes the coroutine execution. Finally by executor.execute()
starting the thread pool.
internal abstract class ExecutorCoroutineDispatcherBase : ExecutorCoroutineDispatcher(), Delay {
override fun dispatch(context: CoroutineContext, block: Runnable) {
try {
executor.execute(wrapTask(block))
} catch (e: RejectedExecutionException) {
unTrackTask()
DefaultExecutor.enqueue(block)
}
}
}
6.Dispatchers.Default
If you do not specify a scheduler, it will be defaulted DefaultScheduler
. It is actually Dispatchers.IO
the same thread scheduler. This is the thread scheduler:
public actual object Dispatchers {
public actual val Default: CoroutineDispatcher = createDefaultDispatcher()
}
internal actual fun createDefaultDispatcher(): CoroutineDispatcher =
if (useCoroutinesScheduler) DefaultScheduler else CommonPool
Scheduler to use if specified CommonPool
, representing a common pool of shared threads as a coroutine scheduler for compute-intensive tasks.
internal object CommonPool : ExecutorCoroutineDispatcher() {
override val executor: Executor
get() = pool ?: getOrCreatePoolSync()
//执行任务
override fun dispatch(context: CoroutineContext, block: Runnable) {
(pool ?: getOrCreatePoolSync()).execute(wrapTask(block))
}
//创建一个固定大小的线程池
private fun createPlainPool(): ExecutorService {
val threadId = AtomicInteger()
return Executors.newFixedThreadPool(parallelism) {
Thread(it, "CommonPool-worker-${
threadId.incrementAndGet()}").apply {
isDaemon = true }
}
}
CommonPool
A fixed-size thread pool is also created in the middle, dispatch()
by execute()
executing coroutine tasks.
Summarized as follows:
type | Scheduler implementation class | illustrate |
---|---|---|
Dispatchers.Main | HandlerContext | It isDispatchNeeded() returns true, and when the coroutine starts, it is distributed by HandlerDispatcher, and the distribution work in it is done through handler.post(runnable) . |
Dispatchers.IO | DefaultScheduler | It is a thread scheduler, which isDispatchNeeded() returns true, and when it schedules tasks, it executors.execute(runnable) executes runnable tasks through . That is, run the code block in the coroutine to the IO thread. |
Dispatchers.Default | DefaultScheduler,CommonPool | If you do not specify a scheduler, DefaultScheduler will be defaulted , which is actually Dispatchers.IO the same thread scheduler; if you specify a scheduler, it will be the CommonPool shared thread pool. isDispatchNeeded() is true, and executors.execute(runnable) the runnable task is executed through . |
Dispatchers.Unconfined | Unconfined | It isDispatchNeeded() returns false, then its parent class ContinuationInterceptor will not hand over the scheduling of this task to the subclass for execution, but the parent class will execute it immediately in the current thread. |
V. Summary
1. Three-layer packaging of coroutines
Through step-by-step analysis, it was slowly discovered that the coroutine actually has three layers of packaging:
-
The commonly used
launch
andasync
returned onesJob
,Deferred
which encapsulate the state of the coroutine, provide the interface to cancel the coroutine, and their instances are all inherited fromAbstractCoroutine
, which is the first layer of packaging for the coroutine. -
The second layer of packaging is a subclass generated by the compiler
SuspendLambda
, which encapsulates the real operation logic of the coroutine, inherited fromBaseContinuationImpl
, and contains the first layer of packaging, whichcompletion
is the first layer of packaging of the coroutine. -
The third layer of packaging is for the thread scheduling of the coroutine
DispatchedContinuation
, which encapsulates the thread scheduling logic and includes the second layer of packaging of the coroutine.
The three layers of packaging all implement Continuation
the interface, and the various layers of packaging of the coroutine are combined through the proxy mode, and each layer is responsible for different functions.
2. The principle of suspending and resuming coroutines
- When studying the principle of coroutines, it needs to be decompiled into Java files to see the essence. Because part of the code is generated by the kotlin compiler , it cannot be seen in the coroutine source code.
label
Each suspending point corresponds to a case branch (state machine), which is incremented by 1 every time it is called ;label
the default initial value is 0, and it will enter the branch for the first timecase 0
, and the suspending functionCOROUTINE_SUSPENDED
returns directly when returning, then the method execution is ended , the method is suspended.- The code in the coroutine body is called
continuation.resumeWith()
through; after getting the real result, call back to the methodContinuationImpl
in this class , call the method again, enter the state machine case branch, return the real result, and resume the coroutine after the method is restored.resumeWith()
invokeSuspend(result)
- Therefore, the suspension of the coroutine is essentially the suspension of the method, and the suspension of the method is essentially the recovery of the coroutine, which is essentially the
return
recovery of the method, and the essence of the recovery iscallback
the callback.
3. Scheduling principle of coroutine
- The interceptor intercepts the coroutine body every time (resuming) execution of the coroutine body , and then intercepts one
SuspendLambda
through the method of the coroutine distributor and returns another one .interceptContinuation()
Continuation<T>
Continuation<T>
- Encapsulate the intercepted code block as a task
DispatchedContinuation
, and use the toCoroutineDispatcher
determineneedDispatch()
whether it needs to be distributed, and use the method of the subclassdispatch(runnable)
to realize the scheduling work of the coroutine.
4. Frequently asked questions about coroutine interviews
- Interviewer: What is a coroutine?
Coroutine is a solution, a solution to nesting, concurrency, and weakening the concept of threads. It enables better collaboration between multiple tasks, completes asynchronous work in a synchronous manner, and makes asynchronous code as intuitive as synchronous code.
- Interviewer: What is the difference between a coroutine and a thread?
Coroutines are like lightweight threads. Coroutines depend on threads. Multiple coroutines can be created in one thread. The thread is not blocked while the coroutine is suspended . Thread processes are synchronous mechanisms, while coroutines are asynchronous.
- Interviewer: Scheduling principles of coroutines
Specify the scheduler according to the creation of the coroutine HandlerContext
, DefaultScheduler
, UnconfinedDispatcher
to execute the task, so as to solve the thread in which the code in the coroutine runs. HandlerContext
By handler.post(runnable)
distributing to the main thread, DefaultScheduler
the essence is to excutor.excute(runnable)
distribute to the IO thread.
- Interviewer: Is coroutine a thread framework?
The essence of the coroutine is return+callback at compile time, but it provides a scheduler that can run on the IO thread and the scheduler of the main thread when scheduling tasks. Calling a coroutine a thread framework is not accurate enough.
- Interviewer: When to use coroutines?
In the multi-task concurrent process control scenario, the process control is relatively simple, does not involve thread blocking and wake-up, and has higher performance than Java concurrency control methods.
Pay attention, don't get lost
Well everyone, the above is the whole content of this article, thank you very much for reading this article. I am suming, thank you for your support and recognition, your praise is the biggest motivation for my creation. Mountains and rivers meet again , see you in the next article!
My level is limited, and there will inevitably be mistakes in the article. Please criticize and correct me, thank you very much!
Kotlin coroutine learning trilogy:
- "Advanced Kotlin coroutines (1. Foundation building)"
- "Advanced Kotlin coroutines (2, Advanced)"
- "Advanced Kotlin coroutines (3. Principles)"
Reference link:
- Kotlin official website
- "In-depth understanding of Kotlin coroutines"
- "The new version of Kotlin from entry to proficiency" of Muke.com
- "Analysis of the Kotlin Coroutine Mechanism in the Vernacular" by Muke.com
- Kotlin Coroutines (coroutine) complete analysis (2), in-depth understanding of coroutine suspension, recovery and scheduling》