Logical Call Context: Flowing Data across Threads, AppDomains, and Processes

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(nullnullnull));

// 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.

Jeffrey Richter

View Comments

  • If i remember correctly FreeNamedDataSlot only clears the data from the current thread. This means that the data is still present in the thread with the id 3. It's nice that the data automatically flows with you but on the other hand you end up with polluted ThreadPool threads unless you clean them up yourself. IMO the framework should automatically clean ThreadPool threads after they have been used and returned to the pool but unfortunately that's not the case...

  • Logical call context (LCC) is a fantastic mechanism to flow data up and down a logical execution path. However it must be pointed out that when executing under ASP.NET, the LCC falls apart. This is because an ASP.NET request, when under load, can become "thread agile": in other words, ASP.NET will suspend a thread, and move it to another thread without context or TLS for that matter. ASP.NET does move items in the HttpContext.Current.Items dictionary from one thread to the next. So, proper code to keep the LCC concept invariant across ASP.NET and non-ASP.NET application must be cognizant of the execution context. I have created the solution, fully unit tested, and been vetted in production for years; see the end of my comment for the complete code. I use the code below to flow a connection/transaction to mimic the System.Transactions.TransactionScope for ADO.NET and even LINQ to SQL applications without the use of MSDTC (assuming single provider/connection). The code and other goodies can be had at:
    http://code.google.com/p/softwareishardwork/
    *** begin code snippet ***
    /*
    Copyright ©2002-2010 Daniel Bullington
    Distributed under the MIT license: http://www.opensource.org/licenses/mit-license.php
    */
    using System.Runtime.Remoting.Messaging;
    using System.Web;
    namespace SoftwareIsHardwork.Core
    {
    ///
    /// Manages execution path storage of objects in a manner which is safe in
    /// standard executables and libraries and ASP.NET code.
    ///
    public static class ExecutionPathStorage
    {
    #region Properties/Indexers/Events
    ///
    /// Gets a value indicating if the current application domain is running under ASP.NET.
    ///
    public static bool IsInHttpContext
    {
    get
    {
    return (object)HttpContext.Current != null;
    }
    }
    #endregion
    #region Methods/Operators
    private static object AspNetGetValue(string key)
    {
    return HttpContext.Current.Items[key];
    }
    private static void AspNetRemoveValue(string key)
    {
    HttpContext.Current.Items.Remove(key);
    }
    private static void AspNetSetValue(string key, object value)
    {
    HttpContext.Current.Items[key] = value;
    }
    private static object CallCtxGetValue(string key)
    {
    return CallContext.GetData(key);
    }
    private static void CallCtxRemoveValue(string key)
    {
    CallContext.FreeNamedDataSlot(key);
    }
    private static void CallCtxSetValue(string key, object value)
    {
    CallContext.SetData(key, value);
    }
    public static object GetValue(string key)
    {
    if (IsInHttpContext)
    return AspNetGetValue(key);
    else
    return CallCtxGetValue(key);
    }
    public static void RemoveValue(string key)
    {
    if (IsInHttpContext)
    AspNetRemoveValue(key);
    else
    CallCtxRemoveValue(key);
    }
    public static void SetValue(string key, object value)
    {
    if (IsInHttpContext)
    AspNetSetValue(key, value);
    else
    CallCtxSetValue(key, value);
    }
    #endregion
    }
    }
    *** end code snippet ***

  • If i remember correctly FreeNamedDataSlot only clears the data from the current thread. This means that the data is still present in the thread with the id 3. It's nice that the data automatically flows with you but on the other hand you end up with polluted ThreadPool threads unless you clean them up yourself. IMO the framework should automatically clean ThreadPool threads after they have been used and returned to the pool but unfortunately that's not the case...

  • Logical Call Context doesn't work when I want to set some data from a class which is a channel sink.
    public class ServerSink : BaseChannelSinkWithProperties, IServerChannelSink
    {
    public ServerProcessing ProcessMessage(...)
    {
    CallContext.LogicalSetData("TRACE", "true");
    }
    }
    When I get CallContext.LogicalGetData("TRACE") returns null.

  • Hello, Jeffrey.
    My name is Antony Norko, I'm from Belarus (west of Russia).
    I'm a 4th year ASP.NET C# developer and I have a little proposition for you. How about a Russian-translated version of your blog? I can translate it. I suppose it will help russian dev community for better understanding of your blog's content. How you found my idea?
    If you are interested - please contact me via e-mail: anton.norko(at)gmail.com
    Thanks.

  • Jeff, this was exactly I was looking for. I thought... :) Here is the problem. I used this approach to share some data across a multi-threaded application and it worked great. But now the application needs to make a remote call to an external service. The call is failing because my ILogicalThreadAffinative implemenation and the data it contains is not serializable, nor should it be. I don't want it going over the wire on the remote call and even if it did, the remote app does not have access to the assemblies contain the types so they couldn't be deserialized anyway.
    Any suggestions on the right way to solve this problem?

  • Hello Jeffrey,
    Multi threading is used in many situations to improve the performance of the application. When the above mentioned Logical Call Context is used, the performance is hurt because it using serialization and de serialization. In such a case can we create a thread and use it to execute a workflow instead of using threadpool thread and logical call context?
    Regards
    Ram

  • Richter, I have 2 problems : http://social.msdn.microsoft.com/Forums/en-US/netfxbcl/thread/37801713-75d0-4772-a610-9e11b78d89c6
    1、I wrote a winform that with no border and title. I draw the border and title in the client area using some nice pictures. In order to make this window can be moveresizedoubleclick, I override the WndProc method, and handle some msg in it.
    Every thing is go on except that I cannot handle double-click msg. When it has a MaximizeBox, I want it can be double-clicked to be Maximized, when not has MaximizeBox, it just can be moved.
    My code :
    Point p = new Point((int)m.LParam);
    p.X -= this.Left;
    p.Y -= this.Top;
    // In the title area
    if (p.X >= 5 && p.X = 5 && p.Y

Recent Posts

8-Step AWS to Microsoft Azure Migration Strategy

Microsoft Azure and Amazon Web Services (AWS) are two of the most popular cloud platforms.…

3 days ago

How to Navigate Azure Governance

 Cloud management is difficult to do manually, especially if you work with multiple cloud…

1 week ago

Why Azure’s Scalability is Your Key to Business Growth & Efficiency

Azure’s scalable infrastructure is often cited as one of the primary reasons why it's the…

3 weeks ago

Unlocking the Power of AI in your Software Development Life Cycle (SDLC)

https://www.youtube.com/watch?v=wDzCN0d8SeA Watch our "Unlocking the Power of AI in your Software Development Life Cycle (SDLC)"…

1 month ago

The Role of FinOps in Accelerating Business Innovation

FinOps is a strategic approach to managing cloud costs. It combines financial management best practices…

1 month ago

Azure Kubernetes Security Best Practices

Using Kubernetes with Azure combines the power of Kubernetes container orchestration and the cloud capabilities…

2 months ago