协程上下文(CoroutineContext)是整个 Kotlin 协程框架的核心数据结构 。它不是一堆杂乱的配置参数,而是一套精心设计的集合框架。
一、一句话定义
CoroutineContext 是一个以
Key为索引、元素不可变、支持组合的"混入集合"(Mixin Collection) 。
每个协程在创建时都会绑定一个 CoroutineContext,它决定了这个协程在哪儿跑 (调度器)、怎么管理生命周期 (Job)、叫什么名字 (CoroutineName)、出错怎么办(CoroutineExceptionHandler)
CoroutineContext的四大核心元素
| 核心元素 | 作用 | 使用场景 |
|---|---|---|
| 调度器 (CoroutineDispatcher) | 决定协程在哪个线程/线程池执行 | • 切换线程池 • UI主线程 • IO线程 • 自定义线程 |
| 作业 (Job) | 管理协程的生命周期(创建、取消、完成) | • 控制协程取消 • 等待协程完成 • 结构化并发 |
| 协程名称 (CoroutineName) | 调试标识,为协程命名 | • 日志追踪 • 调试分析 • 线程名显示 |
| 异常处理器 (CoroutineExceptionHandler) | 捕获未处理的异常 | • 全局异常处理 • 错误上报 • 用户提示 |
调度器(CoroutineDispatcher)
常见的调度器
| 调度器类型 | 线程模型 | 是否可自定义 | 性能特征 | 典型延迟 | 适用场景 |
|---|---|---|---|---|---|
| Dispatchers.Main | 单线程(UI线程) | ❌ | 极轻量 | <1ms | UI操作、界面更新 |
| Dispatchers.IO | 共享线程池(64线程) | ❌ | 轻量 | 10-100ms | 网络、文件、数据库 |
| Dispatchers.Default | 共享线程池(CPU核心数) | ❌ | 轻量 | 1-10ms | CPU密集型计算 |
| Dispatchers.Unconfined | 调用者线程 | ❌ | 极轻量 | <1ms | 不切换线程的场景 |
| 自定义Dispatcher | 完全控制 | ✅ | 可控 | 自定义 | 专用线程池、隔离环境 |
自定义的Dispatcher
真正可以用在项目中的,自定义的调度器是非常重要的,自定义Dispatcher的本质就是决定block.run()在哪个线程、什么时候被执行!掌握了这一点,你就可以随心所欲地控制协程的执行行为,
内置调度器让你"跑起来",自定义调度器让你"跑得稳、看得清、控得住"。
-
跑得稳:线程隔离、故障隔离、容量可控
-
看得清:完整监控、业务语义、日志追踪
-
控得住:优先级策略、限流策略、可测试
生产环境的核心模块,都应该拥有自己的专属调度器
下面列出相关优势。各位在使用时可以考虑。
| 价值维度 | 内置调度器 | 自定义调度器 | 收益对比 |
|---|---|---|---|
| 线程隔离 | 共享线程池 | 专有线程池 | ⬆️ 故障隔离、性能稳定 |
| 资源控制 | 固定策略 | 可配置策略 | ⬆️ 精准控制、避免耗尽 |
| 监控能力 | 黑盒 | 完全透明 | ⬆️ 可观测、可诊断 |
| 上下文传递 | 基础支持 | 自定义增强 | ⬆️ MDC/Tracing/安全上下文 |
| 业务语义 | 通用命名 | 业务命名 | ⬆️ 快速定位问题 |
| 调度策略 | 标准FIFO | 自定义优先级/限流 | ⬆️ 满足业务SLA |
| 测试能力 | 难以模拟 | 可控调度 | ⬆️ 确定性测试 |
使用场景进行对比
| 场景 | 内置调度器 | 自定义调度器 | 决策 |
|---|---|---|---|
| 简单脚本、Demo | ✅ 完全足够 | ❌ 过度设计 | 内置 |
| Android UI操作 | ✅ Dispatchers.Main | ❌ 没必要 | 内置 |
| 普通业务CRUD | ✅ Dispatchers.IO | ⚠️ 可选 | 内置 |
| CPU密集计算 | ✅ Dispatchers.Default | ⚠️ 可选 | 内置 |
| 核心交易系统 | ❌ 风险高 | ✅ 隔离保护 | 自定义 |
| 全链路追踪 | ❌ 上下文丢失 | ✅ MDC传递 | 自定义 |
| 流量控制 | ❌ 无限制 | ✅ 精准限流 | 自定义 |
| 业务SLA保证 | ❌ FIFO | ✅ 优先级 | 自定义 |
| 金融支付 | ❌ 不可控 | ✅ 审计日志 | 自定义 |
| 单元测试 | ❌ 不确定 | ✅ 可控调度 | 自定义 |
-
使用 ExecutorService.asCoroutineDispatcher()
val customDispatcher = Executors.newFixedThreadPool(10).asCoroutineDispatcher()
基于这种方法,可以完全扩张出各个线程池转化给调度器,具体的细节请查阅Kotlin/Java的线程池的相关创建方法。
PS: 这个可以扩张出各个各样的调度器,例如,单线程的,限流的,高优先级的,低优先级的,日志跟踪分析的,全链路跟踪的,主要跟线程池的功能对齐。
- 继承CoroutineDispatcher, 实现dispatch方法
Kotlin
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Runnable
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicInteger
import kotlin.coroutines.CoroutineContext
class SimpleCustomDispatcher : CoroutineDispatcher() {
// 创建线程池:2个线程,带自定义名称
private val threadPool = Executors.newFixedThreadPool(2) { runnable ->
Thread(runnable, "SimpleDispatcher-${counter.incrementAndGet()}")
}
override fun dispatch(context: CoroutineContext, block: Runnable) {
// 提交任务到线程池执行
threadPool.submit(block)
}
companion object {
private val counter = AtomicInteger(0)
}
}
Job
Job是什么
一句话定义 :Job是协程的句柄 (Handle),用于管理协程的生命周期------创建、取消、完成、等待 。它不是协程本身,而是协程的"遥控器"!
核心本质:
Kotlin
interface Job : CoroutineContext.Element {
// 状态查询
val isActive: Boolean
val isCompleted: Boolean
val isCancelled: Boolean
// 生命周期控制
fun start(): Boolean
fun cancel(cause: CancellationException? = null)
// 等待
suspend fun join()
// 父子关系
fun attachChild(child: ChildJob): ChildHandle
companion object Key : CoroutineContext.Key<Job>
}
Job的六种状态(最核心)
Job的生命周期是一个有限状态机,只有6种状态:
| 状态 | isActive | isCompleted | isCancelled | 是否可执行 | 是否可取消 |
|---|---|---|---|---|---|
| New (新建) | ❌ | ❌ | ❌ | ❌ | ✅ |
| Active (活跃) | ✅ | ❌ | ❌ | ✅ | ✅ |
| Completing (完成中) | ✅ | ❌ | ❌ | ✅ | ✅ |
| Cancelling (取消中) | ❌ | ❌ | ✅ | ❌ | ❌ |
| Cancelled (已取消) | ❌ | ✅ | ✅ | ❌ | ❌ |
| Completed (已完成) | ❌ | ✅ | ❌ | ❌ | ❌ |
状态流转图:

Job的父子关系(结构化并发核心)
最强大也最容易误解的特性!
Kotlin
val parentJob = Job()
val scope = CoroutineScope(parentJob + Dispatchers.Default)
// 启动子协程
val childJob1 = scope.launch {
delay(1000)
}
val childJob2 = scope.launch {
delay(2000)
}
println(parentJob.children.toList()) // [childJob1, childJob2]
父子关系四大铁律
| 规则 | 行为 | 代码示例 | 结果 |
|---|---|---|---|
| ① 父取消 → 子取消 | 父Job取消,所有子Job递归取消 | parentJob.cancel() |
所有子协程立即取消 |
| ② 子失败 → 父取消 | 子协程异常,默认传播给父协程 | launch { throw Exception() } |
父及兄弟协程全取消 |
| ③ 父等待子完成 | 父协程在Completing状态等待所有子协程 | 自动行为 | 父最终完成需等所有子完成 |
| ④ 子不影响父 | 子协程完成/取消,不直接影响父状态 | childJob.cancel() |
父协程继续执行 |
SupervisorJob:打破规则
Kotlin
val supervisor = SupervisorJob()
val scope = CoroutineScope(supervisor + Dispatchers.Default)
scope.launch {
delay(100)
throw Exception("子协程1失败")
}
scope.launch {
delay(200) // ✅ 这个协程会正常执行完!
println("子协程2完成")
}
delay(300)
println("父Job状态: ${supervisor.isActive}") // true!父没被取消
SupervisorJob vs Job:
| 特性 | Job | SupervisorJob |
|---|---|---|
| 子失败是否取消父 | ✅ 是 | ❌ 否 |
| 子失败是否影响兄弟 | ✅ 是(级联取消) | ❌ 否(独立运行) |
| 默认场景 | launch、async |
supervisorScope、viewModelScope |
SupervisorJob的话,子Job是否失败都不会影响父以及兄弟。相当于祸不及家里人,而Job中父就是一个大家长,只有一个子或者父本身出现了问题,那么全家人都遭殃。
JOB实战陷阱与最佳实践
陷阱1:在外部创建Job并传入
Kotlin
// ❌ 极度危险的错误用法!
val job = Job()
scope.launch(job) {
// 这个协程有自己的Job(子Job)
// 传入的job成为父Job
}
job.cancel() // 这会取消整个作用域!
// ✅ 正确做法
val job = Job() // 作为父Job
val scope = CoroutineScope(job + Dispatchers.Main)
scope.launch { ... } // 自动成为子Job
// 需要取消时:
scope.cancel() // 或 job.cancel()
陷阱2:Job不会自动等待子Job
Kotlin
/ ❌ 错误
val parentJob = Job()
val childJob = launch(parentJob) {
delay(1000)
}
parentJob.join() // ⚠️ 立即返回!不会等待childJob!
// ✅ 正确
parentJob.children.forEach { it.join() } // 等待所有子Job
// 或
parentJob.cancelAndJoin() // 取消并等待完成
陷阱3:取消是协作式的
Kotlin
// ❌ 无法取消的协程
val job = launch {
var i = 0
while (i < Int.MAX_VALUE) { // 没有挂起点,永不响应取消
// 协程如果不主动检查,永远不知道自己被取消了
// 没有任何操作会强制打断它!
i++
}
println("完成")
}
delay(10)
job.cancel() // ❌ 无效!协程会继续运行,只是把状态标记为Cancelled,协程不检查就无效
// ✅ 正确:定期检查取消状态
val job = launch {
var i = 0
while (i < Int.MAX_VALUE && isActive) { // 检查取消
i++
}
println("完成")
}
陷阱4:finally块的特殊性
Kotlin
val job = launch {
try {
delay(1000)
} finally {
// ✅ 这里可以执行普通代码
println("清理资源")
// ❌ 这里不能调用挂起函数!
delay(100) // 会抛出CancellationException
// ✅ 如果需要挂起,用withContext(NonCancellable)
withContext(NonCancellable) {
delay(100) // 这是允许的
println("强制完成的清理")
}
}
}
delay(500)
job.cancel()
// 当协程被取消时:
// 1. Job的状态从 Active -> Cancelling
// 2. 所有挂起点都会立即抛出 CancellationException
// 3. finally块被执行,但此时协程已处于"正在取消"状态
当协程进入Cancelling状态后,任何挂起函数都会立即抛出CancellationException!
CoroutineName
就是被协程起一个名字,是一个纯调试用的上下文元素,它的唯一作用就是给协程起个名字!可以用来分析日志,跟踪问题。
CoroutineExceptionHandler
CoroutineExceptionHandler 是协程的全局异常捕获器,用于处理未捕获的异常------但它只对launch创建的根协程有效!极简接口:就一个方法,两个参数------上下文和异常!
Kotlin
public interface CoroutineExceptionHandler : CoroutineContext.Element {
public companion object Key : CoroutineContext.Key<CoroutineExceptionHandler>
// 唯一的核心方法!
public fun handleException(context: CoroutineContext, exception: Throwable)
}
Kotlin
private val coroutineExceptionHandler = CoroutineExceptionHandler { context, throwable ->
Log.i("TAG", "null() called with: context = $context, throwable = $throwable")
}
从 CoroutineContext 获取核心元素
| 元素 | Key | 获取代码 | 返回值类型 | 注意事项 |
|---|---|---|---|---|
| Job | Job.Key |
coroutineContext[Job] |
Job? |
每个协程必有,不会为null |
| CoroutineName | CoroutineName.Key |
coroutineContext[CoroutineName] |
CoroutineName? |
可能为null |
| Dispatcher | ContinuationInterceptor.Key |
coroutineContext[ContinuationInterceptor] |
ContinuationInterceptor? |
需强转或as? |
| CoroutineExceptionHandler | CoroutineExceptionHandler.Key |
coroutineContext[CoroutineExceptionHandler] |
CoroutineExceptionHandler? |
Kotlin
val customDispatcher = Executors.newFixedThreadPool(10).asCoroutineDispatcher()
private val context = SupervisorJob() + customDispatcher + CoroutineName("xxName_YY")
private val scope = CoroutineScope(context)
fun foo() {
scope.launch {
println("launch name is ${context[CoroutineName]} job is ${context[Job]} dispatcher is ${context[ContinuationInterceptor]} handler is ${context[CoroutineExceptionHandler]}")
println("launch name is ${coroutineContext[CoroutineName.Key]?.name} job is ${coroutineContext[Job.Key]} dispatcher is ${coroutineContext[ContinuationInterceptor.Key]} handler is ${coroutineContext[CoroutineExceptionHandler.Key]}")
}
}