Many developers are familiar with Thread Local Storage (TLS) as a mechanism allowing you to associate a piece of data with a thread. I usually discourage people from using TLS because I recommend architecting applications to perform their operations using thread pool threads. TLS doesn’t work with the thread pool because different thread pool threads all take part in a since workflow or operation sequence. However, sometimes people would like to associate some data with a workflow that multiple threads participate in. The .NET Framework has a little-known facility that allows you to associate data with a “logical” thread-of-execution. This facility is called logical
call context and it allows data to flow to other threads, AppDomains, and even to threads in other processes.
While the logical call context facility was originally created for use by the .NET Framework’s remoting infrastructure it can certainly be used without using remoting. Here’s how it works, every managed thread has a hashtable object associated with it. For this hastable, the keys are String objects and the values are can refer to any object whose type is marked with the [Serializable] custom attribute. The content of this hashtable is automatically serialized/deserialized when a logical thread of execution crosses a physical thread, appdomain, or process boundary.
To manipulate a thread’s logical call context hashtable, you call the static LogicalSetData and LogicalGetData methods defined on the CallContext class (defined in the System.Runtime.Remoting.Messaging namespace). You delete a key from the hashtable by calling FreeNamedDataSlot. The class looks like this:
[Serializable]
public sealed class CallContext : ISerializable {
public static void LogicalSetData(String name, Object data);
public static Object LogicalGetData(String name);
public static void FreeNamedDataSlot(String name);
// For remoting only
public static void SetHeaders(Header[] headers);
public static Header[] GetHeaders();
}
Here is a small sample application that demonstrates the call context mechanism:
public static class Program {
private const String c_CCDataName = “CCData”;
private static void Main() {
// Set an item in the thread’s call context
CallContext.LogicalSetData(c_CCDataName, “Data=” + DateTime.Now);
// Get the item in the thread’s call context
GetCallContext();
// Show that call context flows to another thread
WaitCallback wc = na => GetCallContext();
wc.EndInvoke(wc.BeginInvoke(null, null, null));
// Show that call context flows to another AppDomain
AppDomain ad = AppDomain.CreateDomain(“Other AppDomain”);
ad.DoCallBack(GetCallContext);
AppDomain.Unload(ad);
// Remove the key to prevent (de)serialization of its value
// from this point on improving performance
CallContext.FreeNamedDataSlot(c_CCDataName);
// Show no data due to the key being removed from the hashtable
GetCallContext();
}
private static void GetCallContext() {
// Get the item in the thread’s call context
Console.WriteLine(“AppDomain={0}, Thread ID={1}, Data={2}”,
AppDomain.CurrentDomain.FriendlyName,
Thread.CurrentThread.ManagedThreadId,
CallContext.LogicalGetData(c_CCDataName));
}
}
When I compile and run this code, I get the following output:
AppDomain=04a-Contexts.exe, Thread ID=1, Data=Data=9/27/2010 2:47:55 PM
AppDomain=04a-Contexts.exe, Thread ID=3, Data=Data=9/27/2010 2:47:55 PM
AppDomain=Other AppDomain, Thread ID=1, Data=Data=9/27/2010 2:47:55 PM
AppDomain=04a-Contexts.exe, Thread ID=1, Data=
Since the logical call context mechanism serializes and deserializes objects in a hashtable, it is a pretty expensive mechanism. That is, it takes time to serialize & deserialize objects and, of course, deserializing objects creates objects in the managed heap which must ultimately be garbage collected. So, in my own applications, I try to avoid the call context mechanism just like I try to avoid TLS. However, there are occasions where call context can fill a need and save the day.