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

时间:2017-09-14 20:08:44

标签: kotlin coroutine kotlin-coroutines

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

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

(问题的灵感来自GitHub issue

1 个答案:

答案 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提供开箱即用的支持。