introduction
In the first article, we analyzed the start process of coroutine startup creation process. In this article, we will focus on analyzing the logical process of coroutine scheduling in coroutines. The main analysis and answer the following two questions:
- What is involved in how coroutine methoders schedule coroutine code to a specific thread execution?
- After the child coroutine is executed, how do you switch 0 back to the thread environment of the parent coroutine?
1. The function of coroutine
1.1 Test code
{ //Coecho Body 1 (TAG, "before suspend job.") withContext() { //Coecho Body 2 (TAG, "print in Main thread.") } (TAG, "after suspend job.") }
- In this coroutine test case, we default
launch
A coroutine, we simplylaunch
The outer logic that needs to be executed isCoroutine Body 1。 - In coroutine body 1, we use
withContext
Switch the coroutine to the main thread execution and print the log. We will use the coroutine logic executed here asCoroutine Body 2。 - After the execution of coroutine body 2 is completed, the coroutine body 1 is switched back to execution and the log is printed.
- Note that according to the analysis in our previous article "Creation and Startup of Coroutines", the Kotlin compiler generates an inheritance and
SuspenLamabda
Types of such as:class MainActivity#onCreate$1 : SuspenLambda{...}
. When we talk about coroutine bodies, we also refer to this class instance at the same time.
Continue to tracklaunch()
Function execution logic, this time the tracking process is different from the "Creation and Startup of Coroutines". We will focus on how the coroutine scheduler works during the startup process? See 1.2 next
1.2
public fun ( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = , block: suspend CoroutineScope.() -> Unit ): Job { //1. See 1.2.1 val newContext = newCoroutineContext(context) val coroutine = if () LazyStandaloneCoroutine(newContext, block) else StandaloneCoroutine(newContext, active = true) //2. See 1.3 for details (start, coroutine, block) return coroutine }
- A new one will be created here
CoroutineContext
, see 1.2.1 for details - According to previous analysis, this will eventually be called
startCoroutineCancellable()
For details, see Process 1.3.
1.2.1 newCoroutineContext
public actual fun (context: CoroutineContext): CoroutineContext { val combined = foldCopies(coroutineContext, context, true) val debug = if (DEBUG) combined + CoroutineId(COROUTINE_ID.incrementAndGet()) else combined return if (combined !== && combined[ContinuationInterceptor] == null) debug + else debug }
coroutineContext
:coroutineContext
yesCoroutineScope
member variable, when==EmptyCoroutineContext
context
: Due to the calllaunch
No specifiedContext
, so it is also transmitted hereEmptyCoroutineContext
。foldCopies()
The function adds and copies 2 contexts, and finallycombied==EmptyCoroutineContext
。
And in the final judgment, the return isdebug+
, so the default distributor is。
The coroutine Context operation involved here does not conduct in-depth analysis. It can be simply assumed that the coroutine rewrites the "+" operation, so that "+" can be used to superimpose between the Contexts. No Element types will be added to the Element collection, and existing Element types in the collection will be overwritten.
1.3 startCoroutineCancellable
internal fun <R, T> (suspend (R) -> T).startCoroutineCancellable( receiver: R, completion: Continuation<T>, onCancellation: ((cause: Throwable) -> Unit)? = null ) = runSafely(completion) { //1. Create SuspendLambda coroutine createCoroutineUnintercepted(receiver, completion) //2. Intercept: Take out the distributor and build the method Continuation. See 1.3.1 for details .intercepted() //3. Call the resume method of the method Continuation, see 1.4 for details. .resumeCancellableWith((Unit), onCancellation) }
- The construction of coroutine body here has been analyzed in the section "Creation and Starting of Coroutines" and will not be repeated.
- Intercept, note: Here, another method will be built according to the method
DispatchedContinuation
Object, it is also a continuum type, which is a wrapper for the coroutine body. See Section 1.3.1 for details. - Calling the interceptor continuation
resumeCancellableWith()
The state machine flow begins, and see Section 1.4 for details on the execution of the distribution process.
1.3.1 intercepted()
public fun intercepted(): Continuation<Any?> = intercepted?: ( //1. Remove the interceptor context[ContinuationInterceptor]? //2. Build the interceptor continuation .interceptContinuation(this)?: this) .also { intercepted = it }
- Take out the interceptor type in the current context. According to the analysis in the previous section 1.2.1, what is taken here is
。
-
interceptContinuation(this)
To build the interceptor continuum, pay attention to the incoming herethis
yesCoroutine Body 1.See 1.3.2 for details.
1.3.2 CoroutineDispatcher
//Base class to be extended by all coroutine dispatcher implementations. public abstract class CoroutineDispatcher : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor { public final override fun <T> interceptContinuation(continuation: Continuation<T>): //See 1.4 for details Continuation<T> = DispatchedContinuation(this, continuation) }
Created a new one directlyDispatchedContinuation
Here you need to pay attention to the incoming construction parameters for object instances:
- This: Currently
Dispatcher
, that is。
- continuation:Coroutine Body 1.
1.3.3 Summary
Since then()
The method ends with the analysis, and the final result is: using the contextDispatcher
and currentContination
The object is coroutine body 1. As a construction parameter, a new one was createdDispatchedContinuation
Object.
Next, the third point in 1.3 is called()
Methods begin analysis.
1.4 DispatchedContinuation
internal class DispatchedContinuation<in T>( //1. Distributor @JvmField val dispatcher: CoroutineDispatcher, //2. Note that here the implementation of Continuation is delegated to the continuation member variable. @JvmField val continuation: Continuation<T> ) : DispatchedTask<T>(MODE_UNINITIALIZED) , CoroutineStackFrame, Continuation<T> by continuation { //3. Rewrite the attribute delegate for yourself override val delegate: Continuation<T> get() = this ... // We inline it to save an entry on the stack in cases where it shows (unconfined dispatcher) // It is used only in Continuation<T>.resumeCancellableWith @Suppress("NOTHING_TO_INLINE") inline fun resumeCancellableWith( result: Result<T>, noinline onCancellation: ((cause: Throwable) -> Unit)? ) { val state = (onCancellation) //Default is true if ((context)) { _state = state resumeMode = MODE_CANCELLABLE //4. See for details (context, this) } else { executeUnconfined(state, MODE_CANCELLABLE) { if (!resumeCancelled(state)) { resumeUndispatchedWith(result) } } } } }
Heredispatcher==
, so the next step is to parseWhat exactly is it? See 1.5 for details
- Member variables
dispatcher==
。 - Member variables
continucation==CoecleBody 1 (SuspenLambda type instance)
. at the same timeDispatchedContinuation
Inherited fromContinuation
interface, it willContinuation
The implementation of the interface is delegated to member variablescontinuation
。 -
deleagte
For rewriteattribute, return it to itself.
- Calling the distributor is
of
dispatch()
Method, note the parameters passed here:
context
: FromContinuation
The properties of the interface are delegated to member variablescontinuation
, so thiscontext
==。
this
: The distributor itself
Since then, the analysis of this method ends: the distributor is called for distribution, and then the analysis begins to analyze the coroutine method.CoroutineDispatcher
1.5 DefaultScheduler
// @JvmStatic public actual val Default: CoroutineDispatcher = DefaultScheduler // // Instance of internal object DefaultScheduler : SchedulerCoroutineDispatcher( CORE_POOL_SIZE, MAX_POOL_SIZE, IDLE_WORKER_KEEP_ALIVE_NS, DEFAULT_SCHEDULER_NAME ) { ... }
In fact, it's inheritanceSchedulerCoroutineDispatcher
type. See 1.5.1 for details
1.5.1 SchedulerCoroutineDispatcher
internal open class SchedulerCoroutineDispatcher( private val corePoolSize: Int = CORE_POOL_SIZE, private val maxPoolSize: Int = MAX_POOL_SIZE, private val idleWorkerKeepAliveNs: Long = IDLE_WORKER_KEEP_ALIVE_NS, private val schedulerName: String = "CoroutineScheduler", ) : ExecutorCoroutineDispatcher() { override val executor: Executor get() = coroutineScheduler // This is variable for test purposes, so that we can reinitialize from clean state private var coroutineScheduler = createScheduler() private fun createScheduler() = //1. See 1.5.2 for details CoroutineScheduler(corePoolSize, maxPoolSize, idleWorkerKeepAliveNs, schedulerName) //2. See 1.5.2 for details override fun dispatch(context: CoroutineContext, block: Runnable): Unit = (block) ... } // //2. In fact, it inherits ExecutorCoroutineDispatcherpublic abstract class ExecutorCoroutineDispatcher: CoroutineDispatcher(), Closeable { ... }
- You can see that it actually called
method. At this time, the second parameter is
Runnable
type, and in subsection 1.4 we know that what is passed in isthis
That isDispatchedContinuation
,soDispatchedContinuation
In the inherited parent class, there must be inheritanceRunnable
The interface, and the implementation of its run method is also in the parent class. We temporarily press the table of this, and then continue to follow it.(block)
。
1.5.2 CoroutineScheduler
internal class CoroutineScheduler( @JvmField val corePoolSize: Int, @JvmField val maxPoolSize: Int, @JvmField val idleWorkerKeepAliveNs: Long = IDLE_WORKER_KEEP_ALIVE_NS, @JvmField val schedulerName: String = DEFAULT_SCHEDULER_NAME ) : Executor, Closeable { ... override fun execute(command: Runnable) = dispatch(command) fun dispatch(block: Runnable, taskContext: TaskContext = NonBlockingContext, tailDispatch: Boolean = false) { trackTask() // this is needed for virtual time support val task = createTask(block, taskContext) // try to submit the task to the local queue and act depending on the result val currentWorker = currentWorker() val notAdded = (task, tailDispatch) if (notAdded != null) { if (!addToGlobalQueue(notAdded)) { // Global queue is closed in the last step of close/shutdown -- no more tasks should be accepted throw RejectedExecutionException("$schedulerName was terminated") } } val skipUnpark = tailDispatch && currentWorker != null // Checking 'task' instead of 'notAdded' is completely okay if ( == TASK_NON_BLOCKING) { if (skipUnpark) return signalCpuWork() } else { // Increment blocking tasks anyway signalBlockingWork(skipUnpark = skipUnpark) } } }
- This class inherits
Executor
class, and its construction parameters can be seen as the parameters of the thread pool, so we can know that this is actually a thread pool implemented by the Kotlin coroutine, so we won't follow up. -
execute()
The process is alsodispatch
Process: Deliver the task to the task queue, and then notify the thread to fetch the task execution. Since then, the thread switching action has been completed. - Executed in a new thread
Runnable
For the calling code in 1.4:(context, this)
In-housethis
, that isDispatchedContinuation
。Not implemented
run
The method must be implemented by the parent class he inheritedRunnable
The interface is implemented, so you need to continue looking at the parent class it inherits:DispatchedTask
kind.
1.6 ()
internal abstract class DispatchedTask<in T>( @JvmField public var resumeMode: Int ) : SchedulerTask() { ... internal abstract val delegate: Continuation<T> @Suppress("UNCHECKED_CAST") internal open fun <T> getSuccessfulResult(state: Any?): T = state as T internal open fun getExceptionalResult(state: Any?): Throwable? = (state as? CompletedExceptionally)?.cause public final override fun run() { assert { resumeMode != MODE_UNINITIALIZED } // should have been set before dispatching val taskContext = var fatalException: Throwable? = null try { val delegate = delegate as DispatchedContinuation<T> //1. Remove the agent's continuation val continuation = withContinuationContext(continuation, ) { val context = val state = takeState() // NOTE: Must take state in any case, even if cancelled val exception = getExceptionalResult(state) val job = if (exception == null && ) context[Job] else null if (job != null && !) { val cause = () cancelCompletedResult(state, cause) (cause) } else { if (exception != null) { (exception) } else { //1. The resume method of the wrapped continuum, and it really starts to start its coroutine state machine code. (getSuccessfulResult(state)) } } } } catch (e: Throwable) { // This instead of runCatching to have nicer stacktrace and debug experience fatalException = e } finally { val result = runCatching { () } handleFatalException(fatalException, ()) } } }
- Will
delegate
Turn toDispatchedContinuation
, you should pay attention to the subsection 1.4DispatchedContinuation
inheritDispatchTask
When this isdelegate
Rewrite:
override val delegate: Continuation
get() = this
And thisThat's the first new
DispatchedContinuation(this)
This is passed in at the time, this is generated by the Kotlin compiler for the coroutine body at the beginningSuspendLambda
Type object. For details, you can look back at section 1.3.
- Called
()
The method triggers the coroutine's state machine and then starts executing the coroutine business logic code. Combined with the previous analysis of 1.5.2, we can know that the call of this method has beendispatch
Go to a specific thread and execute after completing thread switching. Therefore, the code of the coroutine state machine is also running on the new thread.
1.7 Summary
At this point, the thread scheduling analysis of the coroutine has ended. The key points are as follows:
- create
SuspendLambda
When his coroutine context object comes from, default is
。
-
SuspendLambda
Called at startupintercept()
Make a layer of packaging and getDispatchedContinuation
, subsequent coroutine startup is startedDispatchedContinuation
Coroutine. -
DispatchedContinuation
Inherited fromRunnable
Interface, delivers itself to the distributor when coroutine is starteddispatcher
implementrun
method, thus achieving the thread switching effect. - exist
DispatchedContinuation
ofrun
In the method, call()
Start the state machine. Execute coroutine state machine code in a new thread.
In this section, it introduces how to schedule the coroutine to the destination thread to execute. Next, analyze how to switch threads at will and then restore to the original thread.
2. Thread switching in coroutines
In the first section, we figure out how the coroutine scheduler works in it when coroutines are started. This section aims to analyze how the original thread continues to execute the remaining logic after the coroutine uses the distributor to switch threads to execute the new suspend function.
To do this, we need to decompile the 1.1 test code into the actual code and analyze it.
2.1 Decompile code
2.1.1 MainActivityonCreateonCreateonCreate1
final class MainActivity$onCreate$1 extends SuspendLambda implements Function2<CoroutineScope, Continuation<? super Unit>, Object> { ... @Override // public final Object invokeSuspend(Object $result) { Object coroutine_suspended = IntrinsicsKt.getCOROUTINE_SUSPENDED(); switch () { case 0: ($result); (, LiveLiterals$.m4147xf96cab04()); = 1; //1. Create a type that is automatically generated by the new compiler that inherits from SuspendLambda. AnonymousClass1 anonymousClass1 = new AnonymousClass1(null); //2. Call withContext Object res = ((), anonymousClass1, this); if (res != coroutine_suspended) { break; } else { //Hang return coroutine_suspended; } case 1: ($result); break; default: throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine"); } (, LiveLiterals$.m4148xe0c1b328()); return ; } }
According to previous article analysis, heresuspend lambda
All types are automatically generated and inherited fromSuspendLambda
type. See 2.1.2 for details.
WillanonymousClass1
IncomingwithContext
, and note that it has been sent herethis==MainActivity$onCreate$1
, see 2.2 for details.
2.1.2 AnonymousClass1
/* compiled from: */ public static final class AnonymousClass1 extends SuspendLambda implements Function2<CoroutineScope, Continuation<? super Integer>, Object> { int label ... @Override // public final Object invokeSuspend(Object obj) { IntrinsicsKt.getCOROUTINE_SUSPENDED(); switch () { case 0: (obj); return ((, LiveLiterals$.m4146x7c0f011f())); default: throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine"); } } }
2.2 withContext
public suspend fun <T> withContext( context: CoroutineContext, block: suspend CoroutineScope.() -> T ): T { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } //1. Get the current coroutine, note that the uCont here is the current continuum, that is, MainActivity$onCreate$1 return suspendCoroutineUninterceptedOrReturn sc@ { uCont -> //2. Calculate the obtained new coroutine context val oldContext = val newContext = oldContext + context //3. Quick judgment: Quickly deal with the situation where the new context and the old context are consistent. // always check for cancellation of new context () // FAST PATH #1 -- new context is the same as the old one if (newContext === oldContext) { val coroutine = ScopeCoroutine(newContext, uCont) return@sc (coroutine, block) } // FAST PATH #2 -- the new dispatcher is the same as the old one (something else changed) // `equals` is used by design (see equals implementation is wrapper context like ExecutorCoroutineDispatcher) if (newContext[ContinuationInterceptor] == oldContext[ContinuationInterceptor]) { val coroutine = UndispatchedCoroutine(newContext, uCont) // There are changes in the context, so this thread needs to be updated withCoroutineContext(newContext, null) { return@sc (coroutine, block) } } // SLOW PATH -- use new dispatcher //4. Create a new DispatchedCoroutine val coroutine = DispatchedCoroutine(newContext, uCont) //5. Start coroutine (coroutine, coroutine) () } }
-
suspendCoroutineUninterceptedOrReturn
This function is not implemented directly by stepping. Its implementation is generated by the Kotlin compiler. Its function is to obtain the current continuum and passuCont
Return, here isMainActivity$onCreate$1
。 - Put the old coroutine context together with the new context. Calculate the final context. Here
context==()
。 - Quick judgment, no need to look.
- Create a new one
DispatchedCoroutine
, note that the new coroutine context and current continuation object are passed here. - Call
startCoroutineCancellable()
Start coroutine. The same as the analysis in Section 1.3.2, see 2.2.1 for details
2.2.1 startCoroutineCancellable
internal fun <R, T> (suspend (R) -> T).startCoroutineCancellable( receiver: R, completion: Continuation<T>, onCancellation: ((cause: Throwable) -> Unit)? = null ) = runSafely(completion) { //1. Create SuspendLambda coroutine createCoroutineUnintercepted(receiver, completion) //2. Intercept: Take out the distributor and build the method Continuation. See 1.3.1 for details .intercepted() //3. Call the resume method of the method Continuation, see 1.4 for details. .resumeCancellableWith((Unit), onCancellation) }
This method has been analyzed in the previous section 1.3. For this call, the change is that the distributor in the coroutine context has been set to。
- Created
SuspendLambda
object, of this objectCoroutineContext
for. And the
ContinuationInterceptor
typeElement
It's what we sent in before。
- Create a
DispatchedContinuation
。 - Convert coroutines
SuspendLambda
The state machine logic passesScheduled to the main thread for execution, refer to the next section of the scheduling process. For details on the distribution logic, please refer to Section 2.7.
- when
SuspendLambda
State machineinvokeSuspend()
After the logic is executed, it will return to()
, We need to follow this method to analyze it to get how the coroutine switches back to the execution thread of coroutine body 1 after switching to the main thread execution. See 2.3 for details.
2.3 resumeWith
public final override fun resumeWith(result: Result<Any?>) { // This loop unrolls recursion in (param) to make saner and shorter stack traces on resume var current = this var param = result while (true) { // Invoke "resume" debug probe on every resumed continuation, so that a debugging library infrastructure // can precisely track what part of suspended callstack was already resumed probeCoroutineResumed(current) with(current) { val completion = completion!! // fail fast when trying to resume continuation without completion val outcome: Result<Any?> = try { val outcome = invokeSuspend(param) if (outcome === COROUTINE_SUSPENDED) return (outcome) } catch (exception: Throwable) { (exception) } releaseIntercepted() // this state machine instance is terminating if (completion is BaseContinuationImpl) { // unrolling recursion via loop current = completion param = outcome } else { //1. Enter this judgment // top-level completion reached -- invoke and return (outcome) return } } } }
After the state machine is executed, it enters the type judgment of completion. From 2.2 and 2.2.1, it can be seen that the completion passed in wasDispatchedCoroutine
Type, so added to the else branch, called()
, next analyze this method.
Before this, we need to look at itDispatchedCoroutine
See 2.4.1 for details on the inheritance relationship. If you want to directly track the process, you can directly look at 2.4.2.
2.4 DispatchedCoroutine
2.4.1 The inheritance relationship of DispatchedCoroutine
internal class DispatchedCoroutine<in T>( context: CoroutineContext, uCont: Continuation<T> ) : ScopeCoroutine<T>(context, uCont) { }
Inherited fromScopeCoroutine
internal open class ScopeCoroutine<in T>( context: CoroutineContext, @JvmField val uCont: Continuation<T> // unintercepted continuation ) : AbstractCoroutine<T>(context, true, true), CoroutineStackFrame { }
Inherited fromAbstractCoroutine
public abstract class AbstractCoroutine<in T>( parentContext: CoroutineContext, initParentJob: Boolean, active: Boolean ) : JobSupport(active), Job, Continuation<T>, CoroutineScope { }
2.5 Coroutine thread recovery
2.5.1 ()
public final override fun resumeWith(result: Result<T>) { val state = makeCompletingOnce(()) if (state === COMPLETING_WAITING_CHILDREN) return afterResume(state) }
CalledafterResume
Method, this method is inDispatchedCoroutine
Types have specific implementations. See 2.5.2
2.5.2 afterResume
//DispatchedCoroutine override fun afterResume(state: Any?) { if (tryResume()) return // completed before getResult invocation -- bail out // Resume in a cancellable way because we have to switch back to the original dispatcher ().resumeCancellableWith(recoverResult(state, uCont)) }
- Remove the current body
uCont
According to the previous analysis: Section 2.2, we can know that this continuum is equal toMainActivity$onCreate$1
。 -
intercepted()
: Take out its distribution interceptor -
resumeCancellableWith
: Use the method interceptor coroutine body to schedule the state machine logic of the uCont continuum to the corresponding thread environment for execution. Here is the previous one. Note its comment: "Switch it to the original distributor". 2⃣ This process is actually consistent with the process in section 1.3.
- Recover to
When continuing to execute the state machine, since the label has been updated, it will continue to execute and print the last log.
2.6 Summary
withContext()
When starting coroutine, obtain the current coroutine continuationuCount
That isMainActivity$onCreate$1
, a new coroutine will be calculatedcontext
, and then create one with themDispatchedCoroutine
。
AnonymousClass1
When the coroutine starts, useDispatchedCoroutine
Ascompletion
Parameters, then start, at which time the main thread is scheduled to execute the coroutine.
When the coroutine execution is completed,()
The method will be called()
。
()
The method will be called().resumeCancellableWith()
, causing the parent coroutine to schedule and then execute state machine logic.
2.7
@JvmStatic public actual val Main: MainCoroutineDispatcher get() =
See 2.7.1 for details directly
2.7.1 MainDispatcherLoader
internal object MainDispatcherLoader { private val FAST_SERVICE_LOADER_ENABLED = systemProp(FAST_SERVICE_LOADER_PROPERTY_NAME, true) @JvmField val dispatcher: MainCoroutineDispatcher = loadMainDispatcher() private fun loadMainDispatcher(): MainCoroutineDispatcher { return try { val factories = if (FAST_SERVICE_LOADER_ENABLED) { () } else { // We are explicitly using the // `(MyClass::, MyClass::).iterator()` // form of the ServiceLoader call to enable R8 optimization when compiled on Android. // 1. Obtain the implementation class of MainDispatcherFactory ( MainDispatcherFactory::, MainDispatcherFactory:: ).iterator().asSequence().toList() } @Suppress("ConstantConditionIf") { }?.tryCreateDispatcher(factories) ?: createMissingDispatcher() } catch (e: Throwable) { // Service loader can throw an exception as well createMissingDispatcher(e) } } }
- Obtain through ServiceLoad mechanism
MainDispatcherFactory
The implementation class, and in the source code, its implementation class isAndroidDispatcherFactory
- Call
tryCreateDispatcher()
For creating a distributor, see 2.7.2 for details.
2.7.2 AndroidDispatcherFactory
internal class AndroidDispatcherFactory : MainDispatcherFactory { override fun createDispatcher(allFactories: List<MainDispatcherFactory>) = HandlerContext(().asHandler(async = true)) override fun hintOnError(): String = "For tests from kotlinx-coroutines-test module can be used" override val loadPriority: Int get() = Int.MAX_VALUE / 2 }
according tocreateDispatcher
Distribution, the implementation class of the main thread distributor isHandlerContext
Type, used for passingMainLooper
BuiltHandler
. See 2.7.3 for details.
2.7.3 HandlerContext
internal class HandlerContext private constructor( private val handler: Handler, private val name: String?, private val invokeImmediately: Boolean ) : HandlerDispatcher(), Delay { /** * Creates [CoroutineDispatcher] for the given Android [handler]. * * @param handler a handler. * @param name an optional name for debugging. */ constructor( handler: Handler, name: String? = null ) : this(handler, name, false) @Volatile private var _immediate: HandlerContext? = if (invokeImmediately) this else null override val immediate: HandlerContext = _immediate ?: HandlerContext(handler, name, true).also { _immediate = it } override fun isDispatchNeeded(context: CoroutineContext): Boolean { return !invokeImmediately || () != } override fun dispatch(context: CoroutineContext, block: Runnable) { if (!(block)) { cancelOnRejection(context, block) } } override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) { val block = Runnable { with(continuation) { resumeUndispatched(Unit) } } if ((block, (MAX_DELAY))) { { (block) } } else { cancelOnRejection(, block) } } ... }
HandlerContext
Inherited fromHandlerDispatcher
, and hisdispatch
The method, you can see, is to throw the block into the settingsMainLooper
ofhandler
implement. Therefore, the continuum will execute the state machine in the main thread to achieve the purpose of switching to the main thread to execute the coroutine.
The above is the detailed explanation of the thread scheduling example of Kotlin coroutine. For more information about thread scheduling of Kotlin coroutine, please pay attention to my other related articles!