Как использовать код, который полагается на 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
интеграция модуль.