.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).
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.
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.
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.
© 2022 - 2024 — McMap. All rights reserved.