如何使用基于ThreadLocal和Kotlin协程的代码

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

但是,Kotlin协同程序是在不同的线程上发送的,所以如何才能工作呢?

(这个问题受GitHub问题的启发)

协程对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一起使用的调度程序包装起来,并MyContext提供数据的初始值。 这个值将被放入线程的本地线程中,协程被恢复。

 launch(MyContext(MyData(), CommonPool)) { // do something... } 

上面的实现还会跟踪已完成的线程本地的任何更改并将其存储在此上下文中,所以多次调用可以通过上下文共享“线程本地”数据。