某些JVM框架使用ThreadLocal
来存储应用程序的调用上下文,如SLF4j MDC,事务管理器,安全管理器等。
然而,Kotlin协程会在不同的线程上发送,那么它是如何工作的呢?
(问题的灵感来自GitHub issue)
答案 0 :(得分:22)
Coroutine与ThreadLocal
的模拟是CoroutineContext
。
要与ThreadLocal
互操作 - 使用库,您需要实现支持特定于框架的线程本地的自定义ContinuationInterceptor
。
这是一个例子。我们假设我们使用一些依赖于特定ThreadLocal
的框架来存储一些特定于应用程序的数据(在此示例中为MyData
):
val myThreadLocal = ThreadLocal<MyData>()
要与协同程序一起使用,您需要实现一个上下文,该上下文保留当前值MyData
,并在每次在线程上恢复协程时将其放入相应的ThreadLocal
。代码应如下所示:
class MyContext(
private var myData: MyData,
private val dispatcher: ContinuationInterceptor
) : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
dispatcher.interceptContinuation(Wrapper(continuation))
inner class Wrapper<T>(private val continuation: Continuation<T>): Continuation<T> {
private inline fun wrap(block: () -> Unit) {
try {
myThreadLocal.set(myData)
block()
} finally {
myData = myThreadLocal.get()
}
}
override val context: CoroutineContext get() = continuation.context
override fun resume(value: T) = wrap { continuation.resume(value) }
override fun resumeWithException(exception: Throwable) = wrap { continuation.resumeWithException(exception) }
}
}
要在协同程序中使用它,请将要与MyContext
一起使用的调度程序包装起来,并为其提供数据的初始值。该值将被放入恢复协程的线程的本地线程中。
launch(MyContext(MyData(), CommonPool)) {
// do something...
}
上面的实现还会跟踪已完成的线程局部的任何更改并将其存储在此上下文中,因此这种方式多次调用可以通过上下文共享“线程局部”数据。
更新:从kotlinx.corutines
版本0.25.0
开始,可以直接支持将Java ThreadLocal
实例表示为协同上下文元素。有关详细信息,请参阅this documentation。通过kotlinx-coroutines-slf4j
集成模块,还可以对SLF4J MDC提供开箱即用的支持。