Dmytro Serdiuk
12/31/2023, 12:43 AMimport kotlinx.coroutines.*
val myThreadLocal = ThreadLocal.withInitial { "initial" }
fun main() = runBlocking(myThreadLocal.asContextElement()) {
myThreadLocal.set("main")
withContext(Dispatchers.Default + myThreadLocal.asContextElement("test")) {
println(myThreadLocal.get())
myThreadLocal.set("UI")
}
println(myThreadLocal.get())
}
Sometimes the result is test;initial - sometimes test;main
Thank youRob
12/31/2023, 12:54 AMmyThreadLocal
. Dispathers.Default is backed by default by a pool of threads.Rob
12/31/2023, 12:56 AMwithContext
resumes on it may or may not be the thread runBlocking
was on.Dmytro Serdiuk
12/31/2023, 1:31 AMRob
12/31/2023, 1:43 AMasContextElement
. I think when withContext
returns, it's resuming on a different thread which doesn't have modified ThreadLocal with main
.Rob
12/31/2023, 1:45 AMprintln("${Thread.currentThread().name}")
Rob
12/31/2023, 1:51 AMRob
12/31/2023, 1:52 AMasContextElement
overrides of the thread local.Rob
12/31/2023, 1:53 AMasContextElement
Rob
12/31/2023, 1:54 AMDmytro Serdiuk
12/31/2023, 1:59 AMRob
12/31/2023, 1:59 AMRob
12/31/2023, 2:02 AMwithContext(EmptyCoroutineContext)
is consistent. It seems that switching threads or dispatchers doesn't keep the same ThreadLocal
override.Rob
12/31/2023, 2:05 AMmyThreadLocal.set("main")
withContext(Dispatchers.Main) {
println(myThreadLocal.get()) // Prints "main"
myThreadLocal.set("UI")
}
println(myThreadLocal.get()) // Prints "main", not "UI"
Dmytro Serdiuk
12/31/2023, 2:08 AMRob
12/31/2023, 2:09 AMval tl = ThreadLocal.withInitial { "initial" }
runBlocking {
println(tl.get()) // Will print "initial"
// Change context
withContext(tl.asContextElement("modified")) {
println(tl.get()) // Will print "modified"
}
// Context is changed again
println(tl.get()) // <- WARN: can print either "modified" or "initial"
}
Rob
12/31/2023, 2:14 AMRob
12/31/2023, 2:22 AMThreadContextElement
and modifications are not propagated. Is there something specific you are trying to do?Dmytro Serdiuk
12/31/2023, 10:45 AMDmytro Serdiuk
12/31/2023, 10:46 AMRob
12/31/2023, 2:00 PMDmytro Serdiuk
12/31/2023, 2:01 PMDmytro Serdiuk
12/31/2023, 2:02 PMRob
12/31/2023, 2:04 PMDmytro Serdiuk
12/31/2023, 2:09 PMDmytro Serdiuk
12/31/2023, 2:36 PMimport kotlinx.coroutines.*
import kotlin.coroutines.*
val myThreadLocal = ThreadLocal.withInitial { "initial" }
data class ThreadLocalKey(private val threadLocal: ThreadLocal<*>) : CoroutineContext.Key<ThreadLocalElement<*>>
class ThreadLocalElement<T>(
private val value: T,
private val threadLocal: ThreadLocal<T>
) : ThreadContextElement<T> {
override val key: CoroutineContext.Key<*> = ThreadLocalKey(threadLocal)
override fun updateThreadContext(context: CoroutineContext): T {
println("I'm updated")
val oldState = threadLocal.get()
threadLocal.set(value)
return oldState
}
override fun restoreThreadContext(context: CoroutineContext, oldState: T) {
println("I'm restored")
threadLocal.set(oldState)
}
// this method is overridden to perform value comparison (==) on key
override fun minusKey(key: CoroutineContext.Key<*>): CoroutineContext {
return if (this.key == key) EmptyCoroutineContext else this
}
// this method is overridden to perform value comparison (==) on key
public override operator fun <E : CoroutineContext.Element> get(key: CoroutineContext.Key<E>): E? =
@Suppress("UNCHECKED_CAST")
if (this.key == key) this as E else null
override fun toString(): String = "ThreadLocal(value=$value, threadLocal = $threadLocal)"
}
fun main() = runBlocking(ThreadLocalElement(value = "initial", threadLocal = myThreadLocal)) {
myThreadLocal.set("main")
println(coroutineContext)
withContext(Dispatchers.Default) {
println(coroutineContext)
println(myThreadLocal.get())
myThreadLocal.set("UI")
}
println(myThreadLocal.get())
}
According to this test I see restored I sometimes called, sometimes not, depending on the runtimeDmytro Serdiuk
12/31/2023, 2:48 PMDmytro Serdiuk
12/31/2023, 2:49 PMmyThreadLocal.set("main")
withContext(Dispatchers.Main) {
println(myThreadLocal.get()) // Prints "main"
myThreadLocal.set("UI")
}
println(myThreadLocal.get()) // Prints "main", not "UI"
It’s very unclear what this code from documentation explains(Dmitry Khalanskiy [JB]
01/03/2024, 7:53 PMThe context element does not track modifications of the thread-local variableSo, changing a thread-local variable in a child coroutine will not propagate this change back to the parent coroutine.