自定义协程上下文
协程上下文 (CoroutineContext) 是一个类似于 Map<Key, Element> 的不可变集合。 它的每个 Element 既是值,也是键(自引用)。
核心法则:加法与覆盖
上下文支持 + 操作符。
- 右覆盖左: 如果两个元素 Key 相同,右边的会替换左边的。
kotlin
// 1. 指定调度器
val ctx1 = Dispatchers.IO
// 2. 增加命名 (用于调试)
val ctx2 = ctx1 + CoroutineName("DownloadWorker")
// 3. 覆盖调度器 (Main 覆盖 IO)
val ctx3 = ctx2 + Dispatchers.Main调试辅助:CoroutineName
在多线程并发日志中,仅仅看线程名 (DefaultDispatcher-worker-1) 很难分辨业务。 给协程起个名字是最佳实践。
kotlin
launch(CoroutineName("Upload-758")) {
// 配合 DebugProbes 或 Logcat,你能清楚看到 "Upload-758" 挂起在某行
println("Working...")
}自定义元素
你可以创建自己的上下文元素来传递隐式参数(如 UserID, Theme)。
kotlin
// 1. 定义 Key
object CurrentUser : CoroutineContext.Key<CurrentUser>
// 2. 定义 Element
class CurrentUser(val name: String) : AbstractCoroutineContextElement(CurrentUser)
// 3. 使用
launch(CurrentUser("Bruce")) {
val user = coroutineContext[CurrentUser]
println("User: ${user?.name}")
}进阶原理:ContinuationInterceptor
你可能会好奇,Dispatchers.IO 是如何把协程“转移”到线程池的? 秘密就在于 ContinuationInterceptor(续体拦截器)。
它是上下文中最特殊的一个 Element。所有挂起函数在挂起后恢复时,都会经过它的拦截。
kotlin
// 伪代码:Dispatcher 的工作原理
class MyDispatcher : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
// 返回一个包装过的 Continuation
return DispatchedContinuation(this, continuation)
}
}
// DispatchedContinuation.resume() 会做一件事:
// 将 "执行 continuation.resume()" 作为一个 Runnable,提交给线程池正是因为有了拦截器,协程才能在恢复时“神奇”地出现在指定的线程上。