Kotlin Coroutine: Reverse Analysis of Suspend and Resume Principles

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)

Advanced Kotlin Coroutine Combat (2, Advanced)

Advanced Kotlin Coroutine Combat (3. Principles)

(This article requires the knowledge points of the coroutines in the previous two articles as a basis)

Outline of this article

insert image description here

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 returnrecovery of the method, and the essence of the recovery is callbackthe callback.

returnBut we can't see and callbackcallback 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.

ToolsThrough -> kotlin->show kotlin ByteCode in the toolbar of AS , the obtained java bytecode needs to be Decompiledecompiled into java source code by clicking the button:
insert image description here

1. The main structure of the coroutine

1.suspend fun

Let's review the suspend function again:

  • suspendIt is the core keyword of Kotlin coroutine;
  • suspendFunctions 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);
  1. After decompiling, you will find that there is one more Continuationparameter (it is callback), that is to say, you need to pass one when calling the suspend function Continuation, 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 coroutines Continuation.
  2. But how does the compiler determine which methods require callback? It is suspenddistinguished 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.suspendContinuationContinuation
  3. It also changed the return value Userto Object.

2.Continuation

Continuationis 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))

ContinuationThere is a resumeWithfunction 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 Continuationthe recovery call of .

ContinuationSimilar 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. SuspendLambdaIt will create a class, which is Continuationthe implementation class.
insert image description here

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, an Continuation 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 Contiunationare different things. What is passed in completionis actually the body of the coroutine. After the coroutine is executed, it needs a Contiunationcallback 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 completionThe 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 lifecycleScopethe 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 CoroutineScopenew 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.MainLifecycleCoroutineScopeImplCoroutineScopeLifecycleOwnerLifecycle

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.() -> Unitrepresents the coroutine code, which is actually a closure code block.
Three things are done here:

  1. Create a new coroutine context according to the context parameter CoroutineContext;
  2. Create , create Coroutineif the startup mode is , otherwise create ;LazyLazyStandaloneCoroutineStandaloneCoroutine
  3. 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 coroutineContextwith the passed-in context contextinto a new context. ContinuationInterceptorIt 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
StandaloneCoroutineinheriting AbstractCoroutinethe class, and rewriting handleJobException()the method of the parent class. AbstractCoroutineAbstract base class for implementing coroutines in coroutine builders,
implementing interfaces such as Continuation, Joband . So is itself one .CoroutineScopeAbstractCoroutineContinuation
insert image description here

coroutine.start()

start(block, receiver, this)

From the source code above, the coroutine start coroutine.start()method is AbstractCoroutineimplemented 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 completionother parameters CoroutineStartto .

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 AbstractCoroutinewe see completionthat passing is this, that is, AbstractCoroutineitself, that is, Coroutinethe coroutine itself. So this completionis the coroutine body. (This is Continuationthe 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:

  1. Create a new one Continuation.
  2. Adding an interceptor to is also the key to thread scheduling Continuation.ContinuationInterceptor
  3. resumeCancellableWithThe final call continuation.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 SuspendLambdais BaseContinuationImplthe 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 BaseContinuationImpla public method in the class. So who implemented this method? Look at the explanation of the relationship between SuspendLambdaand BaseContinuationImpland 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 SuspendLambdasubclass inherited from , SuspendLambdawhich is Continuationthe 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 SuspendLambdainherits 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() {
    
     ···  }
}

ContinuationImplAlso inherited from BaseContinuationImpl, the concrete implementation of the method is SuspendLambdathe method of :resume()BaseContinuationImplresumeWith()

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:

  1. Call invokeSuspendthe method, execute the real operation logic of the coroutine, and return a result;
  2. If outcomeyes COROUTINE_SUSPENDED, the suspend method is executed in the code block, then continue to suspend;
  3. If completionyes BaseContinuationImpl, there is a suspend method inside, it will enter the loop recursion, and continue to execute the suspension;
  4. If completionnot , the method of BaseContinuationImplthe parent class is actually called .AbstractCoroutineresumeWith

Next, let's look at the implementation AbstractCoroutineof 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 completionis BaseContinuationImplthat 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 completionmainly AbstractCoroutineresponsible for maintaining the state and management of the coroutine, and it resumeWithis to complete the coroutine and resume the caller's coroutine.

Its inheritance relationship is: SuspendLambda -> ContinuationImpl -> BaseContinuationImpl -> Continuation.
insert image description here

So create()the method creates Continuationis a SuspendLambdaobject.
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.ktthe 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 codeIt 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. FunctionCreation 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 , CoroutineScopeall CoroutineContextof 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.CoroutineStartFunction2Object

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 parameters Function2, actually one Continuation;
  • invoke(var1, var2) : Rewrite Funtion.invoke()the method, and call it in a chain through the passed parameters create().invokeSuspend().
  1. From the above, we know that (suspend R.() -> T)it is BaseContinuationImplthe implementation class of , so we will use onCreate()the method to create , and create a new one Continuationthrough the parameter , as a return, this is the created coroutine carrier ; (this is the second layer of packaging of the three-layer packaging)completionFunction2ContinuationContinuation

  2. Then call to resumeWith()start the coroutine, then the method of will be executed, and the method will be executed at this time BaseContinuationImplto execute the real operation logic of the coroutine .resumeWith()invokeSuspend()

The process of creating a coroutine is as follows:
insert image description here

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:

  1. The return value of the function Stringis changed from to Object, and the parameter is added after compiling the function without input parameters Continuation. What we originally needed to do callback, now the compiler does it for us.
  2. According to completioncreate one ContinuationImpl, overwrite invokeSuspend()the method, in this method it calls requestUserInfo()the method again, here it calls itself again (is it amazing), and passes continuationin.
  3. In the switch statement, the default initial value of is 0, and it will enter the branch labelfor 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 0delay()continuationObjectCOROUTINE_SUSPENDED
  4. DelayKt.delay(2000, continuation)If the return result is yes COROUTINE_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 suspendmodified, it may not hang. It is necessary for the code inside to have a return value of COROUTINE_SUSPENDEDsuch a flag after compilation, so case 0when 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_SUSPENDEDit 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_SUSPENDEDreturned.

Follow up DelayKTto see COROUTINE_SUSPENDEDhow is obtained:
insert image description here
find DelayKTthe class (note: no Delay.kt, don’t make a mistake), Decomplie to javaand 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 Objectbeen added . This return value is obtained in :completionvar10000cancellable$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 _decisionthe value of , _decisionthe 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 truereturns 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_SUSPENDEDDelayKt.delay()COROUTINE_SUSPENDEDdelay()

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 userInfoare not returned. So this means that suspendthe 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";
}
  1. Since delay()is ioan operation, after 2000 mm it will be continuationreturned via the callback passed to it.
  2. Calling back to the method ContinuationImplin this class resumeWith()will call invokeSuspend()the method again, and then call requestUserInfo()the method again.
  3. It will enter the switch statement again. Since it case 0is assigned label = 1a value of 1 at for the first time, it will enter case 1the branch this time and return the result result form userInfo.
  4. And requestUserInfo()the return value of is invokeSuspend()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 ContinuationImplinherits from BaseContinuationImpl, and it implements continuationthe 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 BaseContinuationImplresumes resumeWith().

  1. Once invokeSuspend()the method is executed, then requestUserInfo()will be called again, and the return value of invokeSuspend()will be obtained , in which to judge the operation after our method resumes according to the return value of .requestUserInfo()ContinuationImplval outcome = invokeSuspend()requestUserInfo()

  2. If outcomeit is COROUTINE_SUSPENDEDa 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.

  3. If this recovery outcomeis a normal result, it will come to the conclusion completion.resumeWith(outcome)that the currently suspended method has been executed, and the method AbstractCoroutineof its parent class is actually called resumeWith, 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 completionparameter 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:
insert image description here

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";
}
  1. You can see that the decompiled code in the coroutine requestUserInfo()is similar to the decompiled code of the method, and the method Function2is also copied in it invokeSuspend(), and the state machine is also similar.
  2. 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_SUSPENDEDrequestUserInfo()COROUTINE_SUSPENDEDrequestUserInfo()
  3. The principle of coroutine recovery is requestUserInfo()roughly the same as that of recovery. Passed in requestUserInfo(this)when calling .Continuation
  4. Then requestUserInfo()after 2000 milliseconds, the function will call invokeSuspend()back the result to the inside completionof the upper layer when it resumes resumeWith(), then the of the coroutine invokeSuspend(result)will be called back.
  5. The code following the previous suspend logic flows through the state machine. At this point lable = 1enter case 1is assigned to var10000and the rest of the code is executed. So requestUserInfo()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.

insert image description here

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, Continuationsome 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 Runnabletype.

If the coroutine scheduler is passed, the closure code block in the coroutine determines the running thread environment. CoroutineDispatcherThere 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 CoroutineDispatcherexecuted immediately by the current thread.

2. Coroutine distribution

ContinuationCalling on an intercepted resume(Unit)ensures that both execution of the coroutine and completion occur within ContinuationInterceptorthe calling context established by the . And the intercepted continuationis DispatchedContinuationwrapped in one layer: (this is Continuationthe 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)
        }
    }
//·····
}

DispatchedContinuationThe start and resume of coroutines are intercepted, respectively resumeCancellable(Unit)and rewritten resumeWith(Result).

When distribution is required, the method dispatcherof 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))
       }
     //·····
    }
}

DispatchedTaskActually one Runnable.

  1. DispatchedContinuation.continuation.resumeWith()When thread scheduling is required, it will call to start the coroutine after scheduling , where continuationis SuspendLambdathe instance;
  2. When thread scheduling is not required, call directly continuation.resumeWith()to start the coroutine directly.

ContinuationThat is to say, add the interception operation to the created resumeWith()and intercept the running operation of the coroutine:
insert image description here

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 ContinuationInterceptorwill 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.MainInherited from Implement the scheduler MainCoroutineDispatchervia :MainDispatcherLoader.dispatcher

public actual object Dispatchers {
    
    
    @JvmStatic
    public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher
}

MainDispatcherLoaderCreated 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())
    }

MainDispatcherFactoryis 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(), Mainetc. 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 HandlerContextdistributed, and the distribution work in it is handler.post(runnable)completed by distributing to the main thread. When restoring, it is also Dispatchers.Mainrestored through this scheduler. When the task is completed, HandlerDispatcherthe 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
}

DefaultSchedulerThe default scheduler of the coroutine scheduler is a thread scheduler that executes blocking tasks. This scheduler Dispatcher.Defaultshares 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 ExperimentalCoroutineDispatcherimplemented 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)
    //···
}

ExperimentalCoroutineDispatcherDistribute tasks to coroutineScheduler.dispatch()implementations.
CoroutineSchedulerIt 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:

  1. First of all, by Runnablebuilding one Task, this Taskactually implements Runnablethe interface;
  2. Take out the current thread and convert it to Worker, this Workeris Threada class inherited from;
  3. will taskbe submitted to the local queue;
  4. If taskthe process of submitting to the local queue is unsuccessful, it will be added to the global queue;
  5. Create Workera 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 Taskexecution and called task.run().

  1. Take it from the local queue or the global queue Task.
  2. To execute this task, in the end, it is actually calling this Runnablerun method.

That is to say, in Workerthis thread, the run method of this is executed Runnable. Do you remember Runnablewho 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.resumeand 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.IOthe 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 }
    }
}

CommonPoolA fixed-size thread pool is also created in the middle, dispatch()by execute()executing coroutine tasks.
insert image description here

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 DefaultSchedulerCommonPool If you do not specify a scheduler, DefaultScheduler will be defaulted , which is actually Dispatchers.IOthe 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 ContinuationInterceptorwill 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 launchand asyncreturned ones Job, Deferredwhich encapsulate the state of the coroutine, provide the interface to cancel the coroutine, and their instances are all inherited from AbstractCoroutine, 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 from BaseContinuationImpl, and contains the first layer of packaging, which completionis 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 Continuationthe interface, and the various layers of packaging of the coroutine are combined through the proxy mode, and each layer is responsible for different functions.
insert image description here

2. The principle of suspending and resuming coroutines

  1. 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.
  2. labelEach suspending point corresponds to a case branch (state machine), which is incremented by 1 every time it is called ; labelthe default initial value is 0, and it will enter the branch for the first time case 0, and the suspending function COROUTINE_SUSPENDEDreturns directly when returning, then the method execution is ended , the method is suspended.
  3. The code in the coroutine body is called continuation.resumeWith()through; after getting the real result, call back to the method ContinuationImplin 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)
  4. 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 returnrecovery of the method, and the essence of the recovery is callbackthe callback.

3. Scheduling principle of coroutine

  • The interceptor intercepts the coroutine body every time (resuming) execution of the coroutine body , and then intercepts one SuspendLambdathrough 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 to CoroutineDispatcherdetermine needDispatch()whether it needs to be distributed, and use the method of the subclass dispatch(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, UnconfinedDispatcherto execute the task, so as to solve the thread in which the code in the coroutine runs. HandlerContextBy handler.post(runnable)distributing to the main thread, DefaultSchedulerthe 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:

Reference link:

I hope we can become friends, share knowledge and encourage each other on Github and Nuggets ! Keep Moving!

Guess you like

Origin blog.csdn.net/m0_37796683/article/details/126880196