Kotlin 协程线程切换原理 —— 以 Dispatchers.IO 为例

Kotlin 协程线程切换原理 ------ 以 Dispatchers.IO 为例

目录

  1. 核心结论
  2. 关键角色
  3. [launch 启动全流程](#launch 启动全流程 "#launch-%E5%90%AF%E5%8A%A8%E5%85%A8%E6%B5%81%E7%A8%8B")
  4. 关键源码逐层拆解
  5. [delay 之后为何还在 IO 线程](#delay 之后为何还在 IO 线程 "#delay-%E4%B9%8B%E5%90%8E%E4%B8%BA%E4%BD%95%E8%BF%98%E5%9C%A8-io-%E7%BA%BF%E7%A8%8B")
  6. 完整时序图
  7. [DispatchedContinuation 是核心](#DispatchedContinuation 是核心 "#dispatchedcontinuation-%E6%98%AF%E6%A0%B8%E5%BF%83")
  8. [Dispatchers.IO vs Dispatchers.Default](#Dispatchers.IO vs Dispatchers.Default "#dispatchersio-vs-dispatchersdefault")

核心结论

launch(Dispatchers.IO) 的线程切换,本质是把协程体包装成一个 Runnable,然后 post 到目标线程池的任务队列 。主线程调用 launch 后立即返回,协程体由 IO 线程池的某个线程从队列中取出并执行。

没有任何神奇的黑盒,就是一次 executor.submit(runnable)


关键角色

职责
CoroutineDispatcher 线程切换的抽象,实现 ContinuationInterceptor
Dispatchers.IO 共享 IO 线程池(最多 64 个线程)
DispatchedContinuation 把 Continuation 包装成 Runnable,投递到目标线程
ContinuationInterceptor 协程上下文中的拦截器,在协程启动时负责包装 Continuation
StandaloneCoroutine launch 创建的协程对象,持有 CoroutineContext
markdown 复制代码
CoroutineContext
    └── Dispatchers.IO(实现了 ContinuationInterceptor)
            └── 拦截 Continuation,包装成 DispatchedContinuation
                    └── 投递到 IO 线程池

launch 启动全流程

以下面这段代码为例:

kotlin 复制代码
// 主线程点击事件
lifecycleScope.launch(Dispatchers.IO) {
    log("thread = ${Thread.currentThread()} >>> delay: 开始")  // IO 线程
    delay(1000)
    log("thread = ${Thread.currentThread()} >>> delay: 结束")  // 仍在 IO 线程
}

分 5 步拆解

Step 1:launch 合并 CoroutineContext

kotlin 复制代码
// kotlinx/coroutines/Builders.kt
public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    // 合并:lifecycleScope 自带的 Context(Main Dispatcher)+ 传入的 Dispatchers.IO
    // Dispatchers.IO 会覆盖父级的 Dispatcher
    val newContext = newCoroutineContext(context)
    //  最终 newContext 中的 Dispatcher = Dispatchers.IO

    val coroutine = StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

Step 2:coroutine.start() 触发拦截器

kotlin 复制代码
// AbstractCoroutine.start()
coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
    └── block.startCoroutineCancellable(coroutine)
            └── createCoroutineUnintercepted(...)   // 创建原始 Continuation
                    .intercepted()                  // ← 关键!触发拦截器
                    .resumeCancellableWith(Result.success(Unit))

Step 3:intercepted() 包装成 DispatchedContinuation

kotlin 复制代码
// ContinuationImpl.intercepted()
public fun intercepted(): Continuation<Any?> =
    intercepted ?: (
        // 从 Context 取出 ContinuationInterceptor(即 Dispatchers.IO)
        context[ContinuationInterceptor]
            ?.interceptContinuation(this)   // 让 Dispatcher 包装自己
            ?: this
    ).also { intercepted = it }

// CoroutineDispatcher.interceptContinuation()
override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
    DispatchedContinuation(this, continuation)
    // 把原始 Continuation 包进 DispatchedContinuation(一个 Runnable)

Step 4:dispatch() 投递到 IO 线程池

kotlin 复制代码
// DispatchedContinuation.resumeCancellableWith()
override fun resumeCancellableWith(result: Result<T>) {
    val state = result.toState()
    // 判断是否需要 dispatch(当前线程 ≠ 目标线程时,需要)
    if (dispatcher.isDispatchNeeded(context)) {
        this.result = state
        // 把自己(Runnable)投递到 Dispatchers.IO 的线程池
        dispatcher.dispatch(context, this)
        //         ↑ Dispatchers.IO 把这个 Runnable 扔进 IO 线程池队列
    } else {
        // 已经在目标线程,直接执行(优化路径)
        executeUnconfined(state, MODE_CANCELLABLE)
    }
}

Step 5:IO 线程取出任务,执行协程体

kotlin 复制代码
// IO 线程池的某个线程取出任务:
DispatchedContinuation.run()  // Runnable.run()
    └── continuation.resumeWith(result)        // 恢复 Continuation
            └── invokeSuspend(Unit)             // 进入协程状态机
                    └── 执行协程体(此时已在 IO 线程)
                            log("thread = IO线程")
                            delay(1000)  ...

关键源码逐层拆解

DispatchedContinuation ------ 线程切换的载体

kotlin 复制代码
// kotlinx/coroutines/internal/DispatchedContinuation.kt
internal class DispatchedContinuation<in T>(
    val dispatcher: CoroutineDispatcher,  // 持有目标 Dispatcher(IO)
    val continuation: Continuation<T>     // 持有原始协程 Continuation
) : DispatchedTask<T>(MODE_UNINITIALIZED),
    CoroutineStackFrame,
    Continuation<T> by continuation  // 代理原始 Continuation 的接口
{

    // 实现 Runnable.run(),由线程池线程调用
    override fun run() {
        val taskContext = this.taskContext
        var fatalException: Throwable? = null
        try {
            val delegate = delegate as DispatchedContinuation<T>
            val continuation = delegate.continuation

            withContinuationContext(continuation, delegate.countOrElement) {
                val context = continuation.context
                val state = takeState()
                val exception = getExceptionalResult(state)

                val job = if (exception == null && job != null) context[Job] else null
                if (job != null && !job.isActive) {
                    // 协程已取消,不执行
                    val cause = job.getCancellationCause()
                    continuation.resumeWithStackTrace(JobCancellationException(...))
                } else {
                    // ← 就在这里,IO 线程调用 resumeWith,驱动状态机
                    continuation.resumeWith(
                        if (exception != null) Result.failure(exception)
                        else Result.success(getSuccessfulResult(state))
                    )
                }
            }
        } catch (e: Throwable) { ... }
    }
}

Dispatchers.IO 线程池实现

kotlin 复制代码
// IO Dispatcher 底层是一个弹性线程池
// 默认最大线程数 = max(64, CPU核心数)
// 可通过 kotlinx.coroutines.io.parallelism 系统属性调整

public val IO: CoroutineDispatcher = DefaultScheduler.IO

internal object DefaultScheduler : ExperimentalCoroutineDispatcher() {
    val IO = LimitingDispatcher(
        this,
        systemProp(IO_PARALLELISM_PROPERTY_NAME, 64),  // 最多 64 线程
        "Dispatchers.IO",
        TASK_PROBABLY_BLOCKING   // 标记为"可能阻塞"的任务类型
    )
}

// dispatch() 实现:把 DispatchedContinuation(Runnable)扔进线程池
override fun dispatch(context: CoroutineContext, block: Runnable) {
    DefaultScheduler.dispatchWithContext(block, this, false)
    // ↑ 最终调用底层 CoroutineScheduler.dispatch()
    //   把 block 加入工作队列,等待空闲线程取走执行
}

delay 之后为何还在 IO 线程

delay 挂起后恢复时,协程会被再次派发(dispatch),派发用的 Dispatcher 是 Continuation 上下文里绑定的那个 ,也就是启动时指定的 Dispatchers.IO

kotlin 复制代码
lifecycleScope.launch(Dispatchers.IO) {
    // ① 此处在 IO 线程(dispatch 到 IO 线程池)
    log("${Thread.currentThread()}")

    delay(1000)
    // delay 挂起,1000ms 后恢复时:
    // scheduleResumeAfterDelay → 定时器到期 → cont.resumeWith()
    // cont 的 context 中 Dispatcher = Dispatchers.IO
    // → 再次 dispatcher.dispatch(context, this)
    // → 再次投递到 IO 线程池

    // ② 此处仍在 IO 线程(不一定是同一个线程,但一定是 IO 线程池的线程)
    log("${Thread.currentThread()}")
}

关键代码在 CancellableContinuationImpl 恢复时:

kotlin 复制代码
// CancellableContinuationImpl.resumeImpl()
private fun resumeImpl(proposedUpdate: Any?, resumeMode: Int, ...) {
    ...
    // dispatchResume 内部会调用 dispatcher.dispatch()
    // dispatcher 就是协程 Context 里的 Dispatchers.IO
    dispatchResume(resumeMode)
}

完整时序图

sequenceDiagram autonumber participant M as 主线程 (Main) participant F as 协程框架 participant IO as IO 线程池 participant T as 定时器线程 M->>F: lifecycleScope.launch(Dispatchers.IO) { ... } Note over F: 1. 合并 Context,Dispatchers.IO 覆盖父 Dispatcher
2. 创建 StandaloneCoroutine
3. 调用 block.startCoroutineCancellable() F->>F: continuation.intercepted() Note over F: Dispatchers.IO.interceptContinuation(cont)
→ 包装成 DispatchedContinuation(IO, cont) F->>IO: dispatcher.dispatch(context, dispatchedContinuation) Note over IO: 把 DispatchedContinuation(Runnable)
投入 IO 线程池任务队列 M-->>M: launch() 立即返回,主线程不阻塞 ✓ Note over IO: IO 线程空闲,取出任务 IO->>IO: DispatchedContinuation.run() IO->>IO: continuation.resumeWith(Unit) IO->>IO: invokeSuspend(Unit) [label=0] Note over IO: 执行协程体,此时在 IO 线程
log(">>> delay: 开始") IO->>T: delay(1000, continuation)
scheduleResumeAfterDelay(1000ms, cont) Note over T: cont 绑定了 Dispatchers.IO
保存 cont,注册定时器 T-->>IO: return COROUTINE_SUSPENDED Note over IO: IO 线程释放 ✓ Note over T: ⏱ 1000ms 后,定时器到期 T->>F: cont.resumeWith(success(Unit)) Note over F: 检查 cont 的 Context
Dispatcher = Dispatchers.IO
→ 需要 dispatch! F->>IO: dispatcher.dispatch(context, dispatchedCont) Note over IO: 再次投入 IO 线程池队列 Note over IO: IO 线程取出任务 IO->>IO: invokeSuspend(success) [label=1] Note over IO: 从断点继续,仍在 IO 线程
log(">>> delay: 结束") ✓

DispatchedContinuation 是核心

flowchart TD A(["主线程\nlaunch(Dispatchers.IO)"]) B["创建 StandaloneCoroutine\n合并 CoroutineContext"] C["continuation.intercepted()\nDispatchers.IO.interceptContinuation(cont)"] D["DispatchedContinuation\n= 原始 Continuation 包了一层 Runnable"] E["dispatcher.dispatch()\n投递到 IO 线程池队列"] F(["主线程立即返回 ✓"]) G["IO 线程取出任务\nDispatchedContinuation.run()"] H["continuation.resumeWith(Unit)\ninvokeSuspend 开始执行"] I(["协程体运行在 IO 线程 ✓"]) A --> B --> C --> D --> E E --> F E --> G --> H --> I style A fill:#4A90D9,color:#fff,stroke:none style F fill:#27AE60,color:#fff,stroke:none style I fill:#27AE60,color:#fff,stroke:none style D fill:#E8A838,color:#fff,stroke:none style E fill:#8E44AD,color:#fff,stroke:none

Dispatchers.IO vs Dispatchers.Default

对比项 Dispatchers.Default Dispatchers.IO
线程池 共享的 CoroutineScheduler 共享的 CoroutineScheduler(同一个!)
最大线程数 CPU 核心数(通常 4~8) max(64, CPU核心数)
任务类型标记 TASK_NON_BLOCKING TASK_PROBABLY_BLOCKING
适用场景 CPU 密集运算 阻塞型 I/O(网络、文件、数据库)
dispatch() 实现 投入同一调度器,限制并发数 投入同一调度器,允许更多并发

两者底层用的是同一个 CoroutineSchedulerDispatchers.IO 只是放开了最大并发限制,允许更多线程同时阻塞等待 I/O。


三句话总结

步骤 发生了什么
launch 把协程体(Continuation)用 DispatchedContinuation 包装成 Runnablepost 到 IO 线程池
协程体执行 IO 线程从队列取出 Runnable,调用 run()resumeWith()invokeSuspend(),协程体在 IO 线程运行
delay 后恢复 定时器触发后,再次通过 Dispatchers.IO.dispatch() 投递到 IO 线程池,协程仍在 IO 线程继续
相关推荐
小书房2 小时前
Android各版本主要新特性
android
兄弟加油,别颓废了。3 小时前
ctf.show_web3
android
火柴就是我3 小时前
代码记录android怎么实现状态栏导航栏隐藏
android·flutter
梦里花开知多少3 小时前
浅谈ThreadPool
android·面试
帅次3 小时前
单例初始化中的耗时操作如何拖死主线程
android·webview·android runtime
用户0874881999174 小时前
Android 资源类型全解析及四大常用布局资源深度指南
android
火锅鸡的味道4 小时前
解决AOSP工程Android Studio打开卡顿
android·python·android studio
2501_915921434 小时前
2026 iOS 上架新趋势 iOS 发布流程模块化
android·ios·小程序·https·uni-app·iphone·webview