Skip to content

上下文桥接:ThreadLocal

源:ThreadContextElement 参考

当协程在不同线程间跳转时,如果不做处理,原本绑定在线程上的 ThreadLocal 变量(如 UserID, TraceID)会立即丢失。

场景再现

kotlin
val threadLocal = ThreadLocal<String>()
threadLocal.set("UserA")

launch(Dispatchers.IO) {
    // 💥 线程切换了!这里大概率是 null,因为我们到了一个新的 Worker 线程
    println(threadLocal.get()) 
}

解决方案:asContextElement

协程框架提供了一个桥接器,在每次线程切换(恢复)时,自动帮你把数据“搬运”到新线程。

kotlin
val context = threadLocal.asContextElement(value = "UserA")

launch(Dispatchers.IO + context) {
    // ✅ 协程框架自动在切换前设置了 ThreadLocal,执行后自动还原
    println(threadLocal.get()) 
}

MDC 集成 (日志追踪)

在后端或复杂 Android 日志中,常用 SLF4J MDC。

kotlin
// 定义一个 MDCContext 元素
class MDCContext(val map: Map<String, String> = MDC.getCopyOfContextMap()) 
    : ThreadContextElement<Map<String, String>> {
    
    companion object Key : CoroutineContext.Key<MDCContext>
    override val key = Key

    override fun updateThreadContext(context: CoroutineContext): Map<String, String> {
        val oldState = MDC.getCopyOfContextMap()
        MDC.setContextMap(map)
        return oldState ?: emptyMap()
    }

    override fun restoreThreadContext(context: CoroutineContext, oldState: Map<String, String>) {
        MDC.setContextMap(oldState)
    }
}