如何使用依赖于ThreadLocal与Kotlin协同程序的代码

Rom*_*rov 19 coroutine kotlin kotlin-coroutines

一些JVM框架用于ThreadLocal存储应用程序的调用上下文,如SLF4j MDC,事务管理器,安全管理器等.

但是,Kotlin协程会在不同的线程上发送,那么它是如何工作的呢?

(问题的灵感来自GitHub问题)

Rom*_*rov 28

Coroutine的模拟ThreadLocalCoroutineContext.

要与ThreadLocal使用库进行互操作,您需要实现ContinuationInterceptor支持特定于框架的线程局部化的自定义.

这是一个例子.让我们假设我们使用一些依赖于特定的框架ThreadLocal来存储一些特定于应用程序的数据(MyData在本例中):

val myThreadLocal = ThreadLocal<MyData>()
Run Code Online (Sandbox Code Playgroud)

要与协同程序一起使用,您需要实现一个保持当前值的上下文,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) }
    }
}
Run Code Online (Sandbox Code Playgroud)

要在协同程序中使用它,请包装要使用的调度程序MyContext并为其提供数据的初始值.该值将被放入恢复协程的线程的本地线程中.

launch(MyContext(MyData(), CommonPool)) {
    // do something...
}
Run Code Online (Sandbox Code Playgroud)

上面的实现还将跟踪已完成的线程局部的任何更改并将其存储在此上下文中,因此这种方式多次调用可以通过上下文共享"线程局部"数据.

更新:从kotlinx.corutines版本开始,0.25.0直接支持将Java ThreadLocal实例表示为协程上下文元素.请参阅此文档以获取详细信 通过kotlinx-coroutines-slf4j集成模块还可以为SLF4J MDC提供开箱即用的支持.