Sunday, November 1, 2009

Archive - Thread Local Storage in .NET

Another repost from my original blog.
Those of you familiar with the intricacies of Win32 multi-threaded programming will be intimately familiar with thread local storage (TLS). And you might have wondered how .NET exposes this often useful construct.
Before we begin I would like to offer a brief description of what TLS is, for those of you that might not have had the fortune of learning Win32 multi-threaded programming. TLS is a means of storing data on a per-thread basis. Any methods accessing the data will access the data associated with the thread in which the method is running. And this is achieved without the method having any special knowledge of the thread in which it is executing.
Now the real question, how can we achieve the same results in the .NET managed environment? Well depending on your situation there are a number of options available. I will describe only 3 options here. First I will describe the more commonly known CallContext and then what in my experience is less widely known Thread Static variables and finally there is the thread data slots.

The CallContext
Most of you are probably familiar with the CallContext class and how it relates to .NET remoting. A CallContext is used to flow data across multiple calls with out having to pass the data along as some part of the argument list of each called method. And if the stored data implements the ILogicalThreadAffinative interface the data will flow across machine boundaries.
A call context is maintained by the .NET framework for each logical thread of execution. By storing data in the call context you are essentially storing data specific to the thread currently executing the method, and any method called in that thread of execution has access to the threads call context. As a simple example, if you wanted to maintain a count of the number of times a function was called per thread you could do some thing like the following.
class Class1
{
static void Main(string[] args)
{
System.Threading.Thread t1 = new System.Threading.Thread(new System.Threading.ThreadStart(ThreadFunction));
System.Threading.Thread t2 = new System.Threading.Thread(new System.Threading.ThreadStart(ThreadFunction));

// Start the threads
t1.Start();
t2.Start();

// Wait for the threads to complete
t1.Join();
t2.Join();

System.Console.Read();
} 

private const string PerThreadCallCounterKey = "CallCounter";
private static Random Rnd = new Random();

private static void ThreadFunction()
{
// Initialize the per-thread CallCounter
System.Runtime.Remoting.Messaging.CallContext.SetData(PerThreadCallCounterKey, 0);
// Pick a randon number of times to call the target method
int iterations = Rnd.Next(255); 
// Call the method iteration number of times
for (int i = 0; i < iterations; ++i)
{
DoTheWork();
}
DisplayCounterValue();
}

private static void DoTheWork()
{
// Get the current threads CallCounter from the CallContext
int counter = (int)System.Runtime.Remoting.Messaging.CallContext.GetData(PerThreadCallCounterKey);

// Increment the counter and store the new value in the CallContext
// Notice the pre-increment (++counter).
System.Runtime.Remoting.Messaging.CallContext.SetData(PerThreadCallCounterKey, ++counter);

//OK, so we updated the counter now do the real work of this method
//...
}

private static void DisplayCounterValue()
{
// Get the current threads CallCounter from the CallContext
int counter = (int)System.Runtime.Remoting.Messaging.CallContext.GetData(PerThreadCallCounterKey);
System.Console.WriteLine("The method was called {0} times from this thread", counter);
}
}


Notice how neither the DoTheWork or DisplayCounterValue methods have any knowledge of the calling thread. They are only aware of context information which is thread specific.

Thread Static Members
As an alternative to using the CallContext class, .NET provides support for what I call Thread Static Members. Thread static members for the most part act like normal static members except that they have per-thread storage rather than per-AppDomain. Declaring a thread static member is very similar to declaring a normal static member, except that the ThreadStaticAttribute attribute is applied to the declaration as follows.



[ThreadStatic]
private static int PerThreadCallCounter;

From this point on any thing that is done to the PerThreadCallCounter member affects the instance of this member associate with the current thread of execution. The following achieves the same results as the earlier example, only this time rather than using the CallContext class a thread static member is used.

class Class1
{
static void Main(string[] args)
{
System.Threading.Thread t1 = new System.Threading.Thread(new System.Threading.ThreadStart(ThreadFunction));
System.Threading.Thread t2 = new System.Threading.Thread(new System.Threading.ThreadStart(ThreadFunction));

// Start the threads
t1.Start();
t2.Start();

// Wait for the threads to complete
t1.Join();
t2.Join();
System.Console.Read();
} 


[ThreadStatic]
private static int PerThreadCallCounter;

private static Random Rnd = new Random();

private static void ThreadFunction()
{ 
// Pick a randon number of times to call the target method 
int iterations = Rnd.Next(255); 

// Call the method iteration number of times
for (int i = 0; i < iterations; ++i)
{
DoTheWork();
}
DisplayCounterValue();
}

private static void DoTheWork()
{
// Increment the static member associated with the current thread
PerThreadCallCounter++;

//OK, so we updated the counter now do the real work of this method
//...
}

private static void DisplayCounterValue()
{
// Display the value of the thread static member to the user
System.Console.WriteLine("The method was called {0} times from this thread", PerThreadCallCounter);
}
}


As you can see and I am sure you will agree this is a much simpler solution. However a word of caution, with this example the naming of the instance becomes critical, sooner or later you will need to come back to a piece of code and if you loose site of the fact that a particular static member is bound to a thread rather than the entire AppDomain you could be in for some interesting debugging in the early hours of the morning.


While thread static members appear to work just like there normal static member counter parts, there are a few caveats. Firstly you might be tempted to initialize your thread static members in a static constructor. Unfortunately the results might surprise you. At first inspection it might appear that your initialization was ignored, however this is not the case, it is just that the static constructor is not run per-thread, but per-AppDomain. That is it is only executed once which means that the Thread Static member is only initialized for one thread, the and the rest are left untouched. And no, you can not apply the ThreadStaticAttribute to the constructor, this attribute can only be applied to member variables (fields).


Of course as apposed to the CallContext solution the ThreadStatic member is limited to the AppDomain boundary, which for most requirements is sufficient.


Thread Data Slots

Thread data slots are allocated once for every thread by using the Thread.AllocateDataSlot(). Once a data slot has been allocated the slot can be used from any thread to access the data stored in the data slot for the executing thread. This most closely matches the workings of the TLS APIs in the Win32 API. Rather than bore you with another rendition of the demonstration application I will leave it to the reader to investigate this solution which is adequately documented in the MSDN documentation.



See Also:

Along similar lines as the ThreadStaticAttribute, you might also want to take a look at the ContextStaticAttribute. Especially for those of you interested in the potential of AOP offered by context bound objects.

No comments:

Post a Comment