Skip to content

自定义协程上下文

协程上下文 (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,提交给线程池

正是因为有了拦截器,协程才能在恢复时“神奇”地出现在指定的线程上。