CoroutineScope 就是协程世界的 Executor,Dispatchers 就是线程策略的实现——从源码角度看懂调度器的继承体系与自定义线程池
// Java 中最原始的方式:手动创建 Thread
Thread {
// 执行任务
TODO()
}.start()
// Kotlin 提供了一个语法糖,底层还是创建 Thread
thread {
TODO()
}
实际开发中不会每次都 new Thread——开销太大。线程池复用已有线程,大幅提升性能:
// 创建一个可缓存的线程池
val executor = Executors.newCachedThreadPool()
executor.execute {
// 任务代码被包装成一个 Runnable 对象
TODO()
}
在 Android 中,经常需要把任务切回主线程更新 UI:
// Handler:向主线程消息队列投递任务
val handler = Handler(Looper.getMainLooper())
handler.post {
// 在主线程执行
}
// View.post:等价于上面的写法
val view: View = findViewById(R.id.xxx)
view.post {
// 也在主线程执行
}
CoroutineScope 在协程中的职责与 Executor 在线程中的职责类似,但功能更多:
CoroutineContext(如 Job、调度器等),调度器底层可能用到线程池CoroutineContext 参数,提供启动协程所需的上下文信息// CoroutineScope = 协程世界的"Executor + 线程策略 + 生命周期管理"
val coroutineScope = CoroutineScope(Job() + Dispatchers.Default)
本质区别:Executor.execute(Runnable) 把代码装进一个 Runnable 对象;而 CoroutineScope.launch { } 把代码装进一个函数类型的对象(suspend CoroutineScope.() -> Unit),编译器会为它生成状态机,支持挂起和恢复。
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit // 函数类型,不是 Runnable
): Job {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
| 对比维度 | Executor.execute(Runnable) | CoroutineScope.launch { } |
|---|---|---|
| 包装形式 | Runnable 对象(无返回值、不可挂起) | suspend 函数类型(可挂起、可恢复) |
| 返回值 | void | Job(可控:cancel、join) |
| 线程切换 | 固定在某线程池执行 | 由 Dispatcher 灵活调度,可随时切换 |
| 生命周期 | 无 | 通过 Job 树实现结构化并发 |
ContinuationInterceptor 就是协程世界中负责"切线程"的那个核心接口。你看到的 Dispatchers.Default、Dispatchers.Main 等,都是它的不同实现。
Dispatchers 中的四个参数都是继承自 CoroutineDispatcher 或其子类,而 CoroutineDispatcher 继承自 ContinuationInterceptor。
public actual object Dispatchers {
@JvmStatic
public actual val Default: CoroutineDispatcher = DefaultScheduler
@JvmStatic
public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher
@JvmStatic
public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined
@JvmStatic
public val IO: CoroutineDispatcher get() = DefaultIoScheduler
@DelicateCoroutinesApi
public fun shutdown() {
DefaultExecutor.shutdown()
// Also shuts down Dispatchers.IO
DefaultScheduler.shutdown()
}
}
| 调度器 | 底层实现 | 适用场景 |
|---|---|---|
| Dispatchers.Default | DefaultScheduler | CPU 密集型任务(排序、加密、解析) |
| Dispatchers.IO | DefaultIoScheduler | I/O 密集型任务(网络、文件、数据库) |
| Dispatchers.Main | MainDispatcherLoader.dispatcher | UI 相关任务(更新界面、操作 View) |
| Dispatchers.Unconfined | kotlinx.coroutines.Unconfined | 不关心线程的轻量操作(测试、底层库) |
更多细节:四种调度器的线程池配置、阿姆达尔定律与弹性线程池设计原理,参见 四种调度器设计原理深度解析。
一般情况下以上三种调度器就足够了。但如果特殊情况需要自己创建线程池,kotlinx-coroutines 提供了两个函数:
// 创建一个固定大小的线程池调度器
val context = newFixedThreadPoolContext(nThreads = 10, name = "MyThreadPool")
该方法返回的是 ExecutorCoroutineDispatcher 对象——它是 CoroutineDispatcher 的子类,并且实现了 Closeable 接口。在不需要该线程池时,记得调用 close() 关闭线程池,避免资源泄漏。
另一个函数是单线程上下文的便捷封装:
@ExperimentalCoroutinesApi
@DelicateCoroutinesApi
public fun newSingleThreadContext(name: String): CloseableCoroutineDispatcher =
newFixedThreadPoolContext(1, name)
它本质上就是 newFixedThreadPoolContext(1, name)——创建一个只有 1 个线程的调度器,适合需要串行执行的场景。
注意:这两个 API 都标记了 @DelicateCoroutinesApi,意味着它们是精细 API,有特殊的使用注意事项(如必须手动关闭)。一般情况下优先使用内置的 Dispatchers。
它还实现了 Closeable 接口,所以可以用 use { } 或手动 close() 来安全释放线程池资源。
newFixedThreadPoolContext(n, name) 创建固定大小线程池,返回 ExecutorCoroutineDispatcher。用完后必须 close()。CoroutineScope 是协程世界的 Executor——它把代码从 Runnable 升级成了可挂起可恢复的 suspend 函数类型。Dispatchers 就是 ContinuationInterceptor 的不同实现,决定了协程跑在哪个线程上。内置四种够用,不够就用 newFixedThreadPoolContext 创建自己的线程池调度器。