如何在 Kotlin 协程中使用依赖于 ThreadLocal 的代码 [英] How to use code that relies on ThreadLocal with Kotlin coroutines

查看:27
本文介绍了如何在 Kotlin 协程中使用依赖于 ThreadLocal 的代码的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Some JVM frameworks use ThreadLocal to store the call context of a application, like the SLF4j MDC, transaction managers, security managers, and others.

但是,Kotlin 协程是在不同的线程上分派的,那么它如何才能工作呢?

However, Kotlin coroutines are dispatched on different threads, so how it can be made to work?

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

推荐答案

Coroutine 与 ThreadLocal 的类比是 CoroutineContext.

Coroutine's analog to ThreadLocal is CoroutineContext.

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

To interoperate with ThreadLocal-using libraries you need to implement a custom ContinuationInterceptor that supports framework-specific thread-locals.

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

Here is an example. Let us assume that we use some framework that relies on a specific ThreadLocal to store some application-specific data (MyData in this example):

val myThreadLocal = ThreadLocal<MyData>()

要将它与协程一起使用,您需要实现一个上下文来保持 MyData 的当前值,并在每次协程运行时将其放入相应的 ThreadLocal在一个线程上恢复.代码应如下所示:

To use it with coroutines, you'll need to implement a context that keeps the current value of MyData and puts it into the corresponding ThreadLocal every time the coroutine is resumed on a thread. The code should look like this:

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 一起包装,并为其提供数据的初始值.该值将被放入协程恢复的线程上的线程局部.

To use it in your coroutines, you wrap the dispatcher that you want to use with MyContext and give it the initial value of your data. This value will be put into the thread-local on the thread where the coroutine is resumed.

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

上面的实现还会跟踪对线程本地所做的任何更改并将其存储在此上下文中,这样多个调用就可以通过上下文共享线程本地"数据.

The implementation above would also track any changes to the thread-local that was done and store it in this context, so this way multiple invocation can share "thread-local" data via context.

UPDATE:从 kotlinx.corutines 版本 0.25.0 开始,直接支持表示 Java ThreadLocal实例作为协程上下文元素.请参阅本文档 详情.还通过 kotlinx-coroutines-slf4j 集成模块对 SLF4J MDC 提供开箱即用的支持.

UPDATE: Starting with kotlinx.corutines version 0.25.0 there is direct support for representing Java ThreadLocal instances as coroutine context elements. See this documentation for details. There is also out-of-the-box support for SLF4J MDC via kotlinx-coroutines-slf4j integration module.

这篇关于如何在 Kotlin 协程中使用依赖于 ThreadLocal 的代码的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆