SoFunction
Updated on 2025-04-07

Detailed explanation of the thread scheduling example of Kotlin coroutine

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 defaultlaunchA coroutine, we simplylaunchThe outer logic that needs to be executed isCoroutine Body 1
  • In coroutine body 1, we usewithContextSwitch 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 andSuspenLamabdaTypes 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 hereCoroutineContext, see 1.2.1 for details
  • According to previous analysis, this will eventually be calledstartCoroutineCancellable()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
}

coroutineContextcoroutineContextyesCoroutineScopemember variable, when==EmptyCoroutineContext

context: Due to the calllaunchNo specifiedContext, so it is also transmitted hereEmptyCoroutineContextfoldCopies()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 methodDispatchedContinuationObject, it is also a continuum type, which is a wrapper for the coroutine body. See Section 1.3.1 for details.
  • Calling the interceptor continuationresumeCancellableWith()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 herethisyesCoroutine 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 directlyDispatchedContinuationHere you need to pay attention to the incoming construction parameters for object instances:

  • This: CurrentlyDispatcher, 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 contextDispatcherand currentContinationThe object is coroutine body 1. As a construction parameter, a new one was createdDispatchedContinuationObject.

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 variablesdispatcher==
  • Member variablescontinucation==CoecleBody 1 (SuspenLambda type instance). at the same timeDispatchedContinuationInherited fromContinuationinterface, it willContinuationThe implementation of the interface is delegated to member variablescontinuation
  • deleagteFor rewriteattribute, return it to itself.
  • Calling the distributor isofdispatch()Method, note the parameters passed here:

context: FromContinuationThe 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 inheritanceSchedulerCoroutineDispatchertype. 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 calledmethod. At this time, the second parameter isRunnabletype, and in subsection 1.4 we know that what is passed in isthisThat isDispatchedContinuation,soDispatchedContinuationIn the inherited parent class, there must be inheritanceRunnableThe 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 inheritsExecutorclass, 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 alsodispatchProcess: 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 threadRunnableFor the calling code in 1.4:(context, this)In-housethis, that isDispatchedContinuationNot implementedrunThe method must be implemented by the parent class he inheritedRunnableThe interface is implemented, so you need to continue looking at the parent class it inherits:DispatchedTaskkind.

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, ())
        }
    }
}
  • WilldelegateTurn toDispatchedContinuation, you should pay attention to the subsection 1.4DispatchedContinuationinheritDispatchTaskWhen this isdelegateRewrite:

override val delegate: Continuation

get() = this

And thisThat's the first newDispatchedContinuation(this)This is passed in at the time, this is generated by the Kotlin compiler for the coroutine body at the beginningSuspendLambdaType 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 beendispatchGo 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:

  • createSuspendLambdaWhen his coroutine context object comes from, default is
  • SuspendLambdaCalled at startupintercept()Make a layer of packaging and getDispatchedContinuation, subsequent coroutine startup is startedDispatchedContinuationCoroutine.
  • DispatchedContinuationInherited fromRunnableInterface, delivers itself to the distributor when coroutine is starteddispatcherimplementrunmethod, thus achieving the thread switching effect.
  • existDispatchedContinuationofrunIn 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 lambdaAll types are automatically generated and inherited fromSuspendLambdatype. See 2.1.2 for details.

WillanonymousClass1IncomingwithContext, 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 &lt;T&gt; withContext(
    context: CoroutineContext,
    block: suspend CoroutineScope.() -&gt; 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 -&gt;
        //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)
        ()
    }
}
  • suspendCoroutineUninterceptedOrReturnThis function is not implemented directly by stepping. Its implementation is generated by the Kotlin compiler. Its function is to obtain the current continuum and passuContReturn, here isMainActivity$onCreate$1
  • Put the old coroutine context together with the new context. Calculate the final context. Herecontext==()
  • Quick judgment, no need to look.
  • Create a new oneDispatchedCoroutine, note that the new coroutine context and current continuation object are passed here.
  • CallstartCoroutineCancellable()Start coroutine. The same as the analysis in Section 1.3.2, see 2.2.1 for details

2.2.1 startCoroutineCancellable

internal fun &lt;R, T&gt; (suspend (R) -&gt; T).startCoroutineCancellable(
    receiver: R, completion: Continuation&lt;T&gt;,
    onCancellation: ((cause: Throwable) -&gt; 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

  • CreatedSuspendLambdaobject, of this objectCoroutineContextfor. And theContinuationInterceptortypeElementIt's what we sent in before
  • Create aDispatchedContinuation
  • Convert coroutinesSuspendLambdaThe 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.
  • whenSuspendLambdaState 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&lt;Any?&gt;) {
        // 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&lt;Any?&gt; =
                    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 wasDispatchedCoroutineType, so added to the else branch, called(), next analyze this method.

Before this, we need to look at itDispatchedCoroutineSee 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)
    }

CalledafterResumeMethod, this method is inDispatchedCoroutineTypes 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 bodyuContAccording 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 toWhen 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 continuationuCountThat isMainActivity$onCreate$1, a new coroutine will be calculatedcontext, and then create one with themDispatchedCoroutine

AnonymousClass1When the coroutine starts, useDispatchedCoroutineAscompletionParameters, 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 mechanismMainDispatcherFactoryThe implementation class, and in the source code, its implementation class isAndroidDispatcherFactory
  • CalltryCreateDispatcher()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 tocreateDispatcherDistribution, the implementation class of the main thread distributor isHandlerContextType, used for passingMainLooperBuiltHandler. 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)
        }
    }
   ...
}

HandlerContextInherited fromHandlerDispatcher, and hisdispatchThe method, you can see, is to throw the block into the settingsMainLooperofhandlerimplement. 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!