Как использовать код, который полагается на ThreadLocal с корутинами Котлина

некоторые фреймворки JVM используют ThreadLocal для хранения контекста вызова приложения, например SLF4j MDC, менеджеры транзакций, менеджеры безопасности и другие.

однако, Kotlin coroutines отправляются в разных потоках, так как это может быть сделано для работы?

(вопрос навеян выпуск GitHub)

1 ответов


аналог Coroutine к ThreadLocal is CoroutineContext.

для взаимодействия с ThreadLocal - используя библиотеки, вам нужно реализовать пользовательский ContinuationInterceptor который поддерживает специфические для фреймворка потоки-locals.

вот пример. Предположим, что мы используем некоторую структуру, которая опирается на определенный 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 и присвоить ей начальное значение ваших данных. Это значение будет помещено в поток-local в потоке, где возобновляется корутина.

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

реализация выше будет также отслеживать любые изменения в локальном потоке, которые были сделаны, и хранить его в этом контексте, поэтому таким образом множественный вызов может обмениваться данными "локального потока" через контекст.

обновление начиная с kotlinx.corutines версия 0.25.0 существует прямая поддержка представления Java ThreadLocal экземпляры как элементы контекста coroutine. См.документация для сведения. Существует также из коробки поддержка SLF4J MDC через kotlinx-coroutines-slf4j интеграция модуль.