Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use code that relies on ThreadLocal with Kotlin coroutines

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

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

(The question is inspired by GitHub issue)

like image 635
Roman Elizarov Avatar asked Sep 14 '17 20:09

Roman Elizarov


People also ask

Can coroutines run on main thread?

We can for example schedule coroutines on a Java Executor or on Android main looper. However, we can't schedule coroutines on just any thread, it has to cooperate.

How do Kotlin coroutines work internally?

A coroutine internally uses a Continuation class to capture the contexts for its execution. Then the dynamic aspect is modeled as a Job class. The use of async usually creates a Deferred job, which is a subclass of the Job class. The CoroutineContext type is required for a coroutine to execute.

How do you use the dispatcher in Kotlin?

Kotlin coroutines use dispatchers to determine which threads are used for coroutine execution. To run code outside of the main thread, you can tell Kotlin coroutines to perform work on either the Default or IO dispatcher. In Kotlin, all coroutines must run in a dispatcher, even when they're running on the main thread.

How do you make sense of Kotlin coroutines?

Block the current thread with “runBlocking” The coroutine builder to block the current thread is called runBlocking : In the context of runBlocking , the given suspending function and its children in the call hierarchy will effectively block the current thread until it finishes executing.

What is a Kotlin coroutine?

A Kotlin Coroutine is a feature in Kotlin that lets you write non-blocking, asynchronous code that doesn’t require context-switching. Who this tutorial is for?

Should you upgrade to the latest version of Kotlin in Android?

What’s noteworthy when it comes to adding Kotlin Coroutines to your project is that they have been stable since the release of Kotlin 1.3.0. So if you’ve been using any version of Kotlin that’s below 1.3.0, it’s recommended that you upgrade the version in Android Studio IDE.

What are the keywords in Kotlin?

In Kotlin, these keywords are not part of the standard library, which is why developers are required to import the kotlinx.coroutines library to make use of them. Apart from that, Kotlin also provides other keywords such as launch, coroutineScope, runBlocking, and more.

What is suspend in Kotlin coroutine?

As already hinted, the Kotlin coroutine library provides an understandable high-level API that lets us start quickly. One new modifier we need to learn is suspend, which is used to mark a method as "suspending". We’ll have a look at some easy examples using APIs from kotlinx.coroutines in the next section.


Video Answer


1 Answers

Coroutine's analog to ThreadLocal is CoroutineContext.

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

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

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

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: 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.

like image 70
Roman Elizarov Avatar answered Oct 07 '22 08:10

Roman Elizarov