Reputation: 51
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?
Upvotes: 4
Views: 1849
Reputation: 101
Unfortunately, ThreadPool does not provide an API to listen to repool events to do this universally. However, if you have control over every place that queues work to the ThreadPool, you can write a simple wrapper to do what you want.
public struct DisposableThreadStatic<T> : IDisposable where T : class, IDisposable
{
[ThreadStatic]
private static T ts_value;
private bool _shouldDispose;
public T Value => ts_value;
public static DisposableThreadStatic<T> GetOrCreate(Func<T> creator)
{
if (ts_value == null)
{
ts_value = creator();
return new DisposableThreadStatic<T>() { _shouldDispose = true };
}
return default;
}
public void Dispose()
{
if (_shouldDispose && ts_value != null)
{
ts_value.Dispose();
ts_value = null;
}
}
}
With this, you can wrap your threaded function with this.
ThreadPool.QueueUserWorkItem(_ =>
{
using var dts = DisposableThreadStatic<MyDisposable>.GetOrCreate(() => new MyDisposable());
// Use value, call any other functions, etc.
dts.Value.Func();
});
And using that same GetOrCreate
call anywhere deeper in the call stack will just return the cached value, and only the top-most call (when the work completes) will dispose it.
Upvotes: 0
Reputation: 29282
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 ThreadStatic
aspect of this since it seems to be a focus of the question.
so any subsequent uses of the particular thread gets a fresh copy of the variable.
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 null
if 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:
public static class HasDisposableThreadStaticThing
{
[ThreadStatic]
public static DisposableThing Foo;
public static void UseDisposableThing()
{
try
{
using (Foo = new DisposableThing())
{
Foo.DoSomething();
}
}
finally
{
Foo = null;
}
}
}
We've accomplished the goal.
Is there any way to reset ThreadStatic variable to default value or null when thread is re used?
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?
public static class HasDisposableThreadStaticThing
{
public static void UseDisposableThing()
{
using (var foo = new DisposableThing())
{
foo.DoSomething();
}
}
}
The result is exactly the same. Every thread starts with a new instance of DisposableThing
because when it executes the method it declares the variable and creates a new instance. Instead of setting it to null
the reference goes out of scope.
The only difference between the two is that in the first example, DisposableThing
is 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.
Upvotes: 1