Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do the semantics of AsyncLocal differ from the logical call context?

.NET 4.6 introduces the AsyncLocal<T> class for flowing ambient data along the asynchronous flow of control. I've previously used CallContext.LogicalGet/SetData for this purpose, and I'm wondering if and in what ways the two are semantically different (beyond the obvious API differences like strong typing and lack of reliance on string keys).

like image 629
ChaseMedallion Avatar asked Jul 29 '15 17:07

ChaseMedallion


People also ask

How does AsyncLocal work?

AsyncLocal class allow asynchronous code to use a kind of async -compatible almost-equivalent of thread local storage. This means that AsyncLocal<T> can persist a T value across an asynchronous flow. Each time you enter an async method a fresh new async context is initiated deriving from its parent async context.

What is async local in C#?

AsyncLocal<T> represents ambient data that is local to a given asynchronous control flow, such as an asynchronous method. It means that your code uses different values when it's accesses from another async method such as WrapperAsync and your main thread contains another value.


3 Answers

The semantics are pretty much the same. Both are stored in the ExecutionContext and flow through async calls.

The differences are API changes (just as you described) together with the ability to register a callback for value changes.

Technically, there's a big difference in the implementation as the CallContext is cloned each time it is copied (using CallContext.Clone) while the AsyncLocal's data is kept in the ExecutionContext._localValues dictionary and just that reference is copied over without any extra work.

To make sure updates only affect the current flow when you change the AsyncLocal's value a new dictionary is created and all the existing values are shallow-copied to the new one.

That difference can be both good and bad for performance, depending on where the AsyncLocal is used.

Now, as Hans Passant mentioned in the comments CallContext was originally made for remoting, and isn't available where remoting isn't supported (e.g. .Net Core) which is probably why AsyncLocal was added to the framework:

#if FEATURE_REMOTING
    public LogicalCallContext.Reader LogicalCallContext 
    {
        [SecurityCritical]
        get { return new LogicalCallContext.Reader(IsNull ? null : m_ec.LogicalCallContext); } 
    }

    public IllogicalCallContext.Reader IllogicalCallContext 
    {
        [SecurityCritical]
        get { return new IllogicalCallContext.Reader(IsNull ? null : m_ec.IllogicalCallContext); } 
    }
#endif

Note: there's also an AsyncLocal in the Visual Studio SDK that is basically a wrapper over CallContext which shows how similar the concepts are: Microsoft.VisualStudio.Threading.

like image 76
i3arnon Avatar answered Oct 14 '22 03:10

i3arnon


I'm wondering if and in what ways the two are semantically different

From what can be seen, both CallContext and AsyncLocal internally rely on ExecutionContext to store their internal data inside a Dictionary. The latter seems to be adding another level of indirection for async calls. CallContext has been around since .NET Remoting and was a convenient way of flowing data between async calls where there wasn't a real alternative, until now.

The biggest difference I can spot is that AsyncLocal now lets you register to notifications via a callback when an underlying stored value is changed, either by a ExecutionContext switch or explicitly by replacing an existing value.

// AsyncLocal<T> also provides optional notifications 
// when the value associated with the current thread
// changes, either because it was explicitly changed 
// by setting the Value property, or implicitly changed
// when the thread encountered an "await" or other context transition.
// For example, we might want our
// current culture to be communicated to the OS as well:

static AsyncLocal<Culture> s_currentCulture = new AsyncLocal<Culture>(
args =>
{
    NativeMethods.SetThreadCulture(args.CurrentValue.LCID);
});

Other than that, one resides in System.Threading while the other lives at System.Runtime.Remoting , where the former will be supported in CoreCLR.

Also, it doesn't seem that AsyncLocal has the shallow copy-on-write semantics SetLogicalData has, so the data flows between calls without being copied over.

like image 34
Yuval Itzchakov Avatar answered Oct 14 '22 03:10

Yuval Itzchakov


There appears to be some semantic difference in timing.

With CallContext the context change happens when the context for the child thread/task/async method is set up, i.e. when Task.Factory.StartNew(), Task.Run() or async method are called.

With AsyncLocal the context change (change notification callback being called) happens when the child thread/task/async method actually starts executing.

The timing difference could be interesting, especially if you want the context object to be cloned when the context is switched. Using different mechanisms could result in different content being cloned: with CallContext you clone the content when the child thread/task is created or async method is called; but with AsyncLocal you clone the content when the child thread/task/async method starts executing, the content of the context object could have been changed by the parent thread.

like image 10
wqiu Avatar answered Oct 14 '22 03:10

wqiu