asContextElement

Wraps ThreadLocal into ThreadContextElement. The resulting ThreadContextElement maintains the given value of the given ThreadLocal for coroutine regardless of the actual thread its is resumed on. By default ThreadLocal.get is used as a value for the thread-local variable, but it can be overridden with value parameter. Beware that context element does not track modifications of the thread-local and accessing thread-local from coroutine without the corresponding context element returns undefined value. See the examples for a detailed description.

Example usage:

val myThreadLocal = ThreadLocal<String?>()
...
println(myThreadLocal.get()) // Prints "null"
launch(Dispatchers.Default + myThreadLocal.asContextElement(value = "foo")) {
println(myThreadLocal.get()) // Prints "foo"
withContext(Dispatchers.Main) {
println(myThreadLocal.get()) // Prints "foo", but it's on UI thread
}
}
println(myThreadLocal.get()) // Prints "null"

The context element does not track modifications of the thread-local variable, for example:

myThreadLocal.set("main")
withContext(Dispatchers.Main) {
println(myThreadLocal.get()) // Prints "main"
myThreadLocal.set("UI")
}
println(myThreadLocal.get()) // Prints "main", not "UI"

Use withContext to update the corresponding thread-local variable to a different value, for example:

withContext(myThreadLocal.asContextElement("foo")) {
println(myThreadLocal.get()) // Prints "foo"
}

Accessing the thread-local without corresponding context element leads to undefined value:

val 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"
}

to fix this behaviour use runBlocking(tl.asContextElement())