I have a ThreadStatic member in a static class. The static class is used in a multi threaded environment. I want to make sure that when a thread is returned to threadpool (or re-used), the member is disposed (or re-initialized), so any subsequent uses of the particular thread gets a fresh copy of the variable. The member has to stay static so an instance member will not really help.
I have tried using ThreadLocal, AsyncLocal and CallContext but none of these really help. (CallContext is mostly for proof of concept, its a .net standard app so callcontext won't work anyways).
This is just a sample code I wrote to recreate my problem having ThreadStatic, ThreadLocal, AsyncLocal and CallContext for testing.
class Program
{
static void Main(string[] args)
{
var act = new List<Action<int>>()
{
v=> ThreadClass.Write(v),
v=> ThreadClass.Write(v),
};
Parallel.ForEach(act, new ParallelOptions { MaxDegreeOfParallelism = 1 }, (val, _, index) => val((int)index));
Console.WriteLine($"Main: ThreadId: {Thread.CurrentThread.ManagedThreadId} ThreadStatic = {ThreadClass.ThreadStatic} ThreadLocal = {ThreadClass.ThreadLocal.Value} AsyncLocal = {ThreadClass.AsyncLocal.Value} CallContext: {ThreadClass.CallContextData}");
Console.ReadKey();
}
}
public static class ThreadClass
{
static object _lock = new object();
[ThreadStatic]
public static string ThreadStatic;
public static ThreadLocal<string> ThreadLocal = new ThreadLocal<string>(() => "default");
public static readonly AsyncLocal<string> AsyncLocal = new AsyncLocal<string>();
public static string CallContextData
{
get => CallContext.LogicalGetData("value") as string;
set => CallContext.LogicalSetData("value", value);
}
static ThreadClass()
{
AsyncLocal.Value = "default";
}
public static void Write(int id)
{
lock (_lock)
{
Console.WriteLine($"{id} Init: ThreadId: {Thread.CurrentThread.ManagedThreadId} ThreadStatic = {ThreadStatic} ThreadLocal = {ThreadLocal.Value} AsyncLocal = {AsyncLocal.Value} CallContext: {ThreadClass.CallContextData}");
ThreadStatic = $"Static({id})";
ThreadLocal.Value = $"Local({id})";
AsyncLocal.Value = $"Async({id})";
CallContextData = $"Call({id})";
Console.WriteLine($"{id} Chng: ThreadId: {Thread.CurrentThread.ManagedThreadId} ThreadStatic = {ThreadStatic} ThreadLocal = {ThreadLocal.Value} AsyncLocal = {AsyncLocal.Value} CallContext: {ThreadClass.CallContextData}");
}
}
}
The above code is run in a single thread so the thread can be re-used.
0 Init: ThreadId: 1 ThreadStatic = ThreadLocal = default AsyncLocal = default CallContext:
0 Chng: ThreadId: 1 ThreadStatic = Static(0) ThreadLocal = Local(0) AsyncLocal = Async(0) CallContext: Call(0)
--------------------
1 Init: ThreadId: 1 ThreadStatic = Static(0) ThreadLocal = Local(0) AsyncLocal = Async(0) CallContext: Call(0)
1 Chng: ThreadId: 1 ThreadStatic = Static(1) ThreadLocal = Local(1) AsyncLocal = Async(1) CallContext: Call(1)
--------------------
Main: ThreadId: 1 ThreadStatic = Static(1) ThreadLocal = Local(1) AsyncLocal = CallContext:
However, as seen in the output, when the second call is made and thread 1 is reused, it still has the values set by thread 0.
Is there any way to reset ThreadStatic variable to default value or null when thread is re used?
TL;DR
If don't want a variable reused by multiple threads in a multi-threaded application, there's no reason to make it static.
If we don't want a variable reused by the same thread, it's questionable why we would deliberately use
[ThreadStatic], since that's what it allows us to do.I'm focusing on the
ThreadStaticaspect of this since it seems to be a focus of the question.Uses of the thread don't need their own copy of the variable - methods that use the variable may or may not need their own copy of the variable. That sounds like a hair-splitting thing to say, but a thread, by itself, doesn't need a copy of any variable. It could be doing things unrelated to this static class and this variable.
It's when we use the variable that we care whether it is a "fresh copy." That is, when we invoke a method that uses the variable.
If, when we use the static variable (which is declared outside of a method), what we want is to ensure that it's newly instantiated before we use it and disposed when we're done with it, then we can accomplish that within the method that uses it. We can instantiate it, dispose it, even set it to
nullif we want to. What becomes apparent as we do this, however, is that it usually eliminates any need for the variable to be declared outside the method that uses it.If we do this:
We've accomplished the goal.
Done. Every time the same thread enters the method ("the thread is re used") it's null.
But if that's what we want, then why not just do this?
The result is exactly the same. Every thread starts with a new instance of
DisposableThingbecause when it executes the method it declares the variable and creates a new instance. Instead of setting it tonullthe reference goes out of scope.The only difference between the two is that in the first example,
DisposableThingis publicly exposed outside of the class. That means that other threads could use it instead of declaring their own variable, which is weird. Since they would also need to make sure it's instantiated before using it, why wouldn't they also just create their own instance as in the second example?The easiest and most normal way to ensure that a variable is initialized and disposed every time it's needed in a static method is to declare that variable locally within the static method and create a new instance. Then regardless of how many threads call it concurrently they will each use a separate instance.