web-dev-qa-db-fra.com

Comment utiliser le code qui s'appuie sur ThreadLocal avec les coroutines Kotlin

Certaines infrastructures JVM utilisent ThreadLocal pour stocker le contexte d'appel d'une application, comme le SLF4j MDC , les gestionnaires de transactions, les gestionnaires de sécurité et autres.

Cependant, les coroutines Kotlin sont réparties sur différents threads, alors comment peut-il fonctionner?

(La question est inspirée de problème GitHub )

22
Roman Elizarov

L'analogue de Coroutine avec ThreadLocal est CoroutineContext .

Pour interagir avec ThreadLocal- à l'aide de bibliothèques, vous devez implémenter un ContinuationInterceptor personnalisé qui prend en charge les sections locales de thread spécifiques au framework.

Voici un exemple. Supposons que nous utilisons un framework qui repose sur un ThreadLocal spécifique pour stocker des données spécifiques à l'application (MyData dans cet exemple):

val myThreadLocal = ThreadLocal<MyData>()

Pour l'utiliser avec des coroutines, vous devrez implémenter un contexte qui conserve la valeur actuelle de MyData et la place dans le ThreadLocal correspondant à chaque reprise de la coroutine sur un thread. Le code devrait ressembler à ceci:

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) }
    }
}

Pour l'utiliser dans vos coroutines, vous encapsulez le répartiteur que vous souhaitez utiliser avec MyContext et lui donnez la valeur initiale de vos données. Cette valeur sera placée dans le thread local sur le thread où la coroutine est reprise.

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

L'implémentation ci-dessus permettrait également de suivre les modifications apportées au thread-local et de les stocker dans ce contexte, de cette façon, plusieurs invocations peuvent partager des données "thread-local" via le contexte.

[~ # ~] mise à jour [~ # ~] : à partir de kotlinx.corutines version 0.25.0 il y a un support direct pour représenter les instances Java ThreadLocal en tant qu'éléments de contexte coroutine. Voir cette documentation pour plus de détails. prise en charge de SLF4J MDC via kotlinx-coroutines-slf4j module d'intégration.

29
Roman Elizarov