If I cannot use TLS in C # async programming, what can I use?

My current practice is to create a whole bunch of contextual information in TLS.

Reading an MSDN article ( http://blogs.msdn.com/b/pfxteam/archive/2012/06/15/executioncontext-vs-synchronizationcontext.aspx ) about capturing the execution context it seems that

a) I can not depend on the continuation running in the same thread that I started on

b) TLS is not cloned in the context of continuation

So, what can I do (except redesign the entire system so as not to use context implicitly related to the execution path). Can I add user data to the execution context that will be captured?

I had a similar problem with TPL, in this case I wrote my own wrapper functions that cloned TLS in TPL-created workers

+4
source share
2 answers

You can use a boolean context for this, which flows as part of the execution context: System.Runtime.Remoting.Messaging.CallContext.LogicalSetData / LogicalGetData .

If you save any volatile data in TLS, note that the logical call context has a copy-to-write behavior and can be forked into several execution paths (and, therefore, can be accessed from several threads at the same time, unlike TLS).

If you store an object, the link to it is copied (rather than a deep clone), so mutable data can be a problem. Any changes will not be propagated outside the scope of the specific copy of the logical call context.

If you really need to distribute the logical call context data to the external context of the caller, and for some reason you cannot use Task.Result , you can still use a mutable data object. You need to add it to the boolean context before you fork the execution thread using async methods (or enter new threads with the API, for example Task.Run , Task.Factory.StartNew , Task.Factory.StartNew , ThreadPool.QueueUserWorkItem , etc. ) Warning: now multiple threads can race to change the value at the same time.

Easy to show with an example:

 using System; using System.Runtime.Remoting.Messaging; using System.Threading.Tasks; namespace ConsoleApplication { class Program { static async Task TestAsync(string id, int delay) { // but we might not even have any asynchrony here // but making the method "async" is already enough // for the copy-on-write behavior to trigger await Task.Delay(delay).ConfigureAwait(false); // copy on write here CallContext.LogicalSetData("name1", "value1-modified-by-" + id); var mutableData = (MutableData<string>)CallContext.LogicalGetData("name2"); Console.WriteLine(CallContext.LogicalGetData("name1")); // racing to set mutableData.Data mutableData.Data = "value2-modified-by-" + id; Console.WriteLine(mutableData.Data); } static void Main(string[] args) { CallContext.LogicalSetData("name1", "value1"); var mutableData = new MutableData<string> { Data = "value2" }; CallContext.LogicalSetData("name2", mutableData); Console.WriteLine(CallContext.LogicalGetData("name1")); Task.WaitAll(TestAsync("A", 1000), TestAsync("B", 1000)); Console.WriteLine(CallContext.LogicalGetData("name1")); Console.WriteLine(mutableData.Data); Console.ReadLine(); } } class MutableData<T> { readonly object _lock = new Object(); T _data = default(T); public T Data { get { lock (_lock) { return _data; } } set { lock (_lock) { _data = value; } } } } } 

For more information, check out Stephen Cleary "Implicit Async Contex" .

I believe that the same behavior applies to .NET 4.6 AsyncLocal , even if it does not use CallContext internally.

+1
source

If you are using .NET 4.6, you can use the AsyncLocal class.

+2
source

All Articles