AsyncLocal的语义与逻辑调用上下文有何不同?

Cha*_*ion 58 .net c# async-await .net-4.6

.NET 4.6引入了AsyncLocal<T>用于沿异步控制流流动环境数据的类.我以前用过CallContext.LogicalGet/SetData这个目的,我想知道两者在语义上是否以及以何种方式不同(除了明显的API差异,如强类型和缺乏对字符串键的依赖).

i3a*_*non 47

语义几乎相同.两者都存储在ExecutionContext异步调用中并通过异步调用流动.

差异是API更改(正如您所描述的)以及为值更改注册回调的功能.

从技术上讲,实现中存在很大的差异,因为CallContext每次复制(使用CallContext.Clone)时都会克隆它,而AsyncLocal数据保存在ExecutionContext._localValues字典中,只需要复制该引用而无需任何额外的工作.

要确保更新仅在更改AsyncLocal值时影响当前流,将创建一个新字典,并将所有现有值浅层复制到新字典中.

这种差异对于性能来说既好又坏,取决于AsyncLocal使用的位置.

现在,正如评论CallContext中提到的Hans Passant 最初用于远程处理,并且在不支持远程处理的情况下不可用(例如.Net Core),这可能是为什么AsyncLocal被添加到框架中:

#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
Run Code Online (Sandbox Code Playgroud)

注意:AsyncLocalVisual Studio SDK中还有一个基本上是一个包装器,CallContext它显示了这些概念的相似之处:Microsoft.VisualStudio.Threading.


Yuv*_*kov 20

我想知道两者在语义上是否以及以何种方式存在差异

从什么可以看出,无论是CallContextAsyncLocal内部依靠ExecutionContext存储里面的内部数据Dictionary.后者似乎为异步调用添加了另一个间接层.CallContext自从.NET Remoting以来一直存在,并且是一种在异步调用之间流动数据的便捷方式,直到现在还没有真正的替代方案.

我能发现的最大区别是AsyncLocal现在允许您通过回调来注册通知,这可以通过ExecutionContext交换机或通过替换现有值来显式更改基础存储值.

// 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);
});
Run Code Online (Sandbox Code Playgroud)

除此之外,一个居住在System.Threading另一个居住地System.Runtime.Remoting,而前者将在CoreCLR中得到支持.

此外,它似乎没有AsyncLocal浅的写时复制语义SetLogicalData,因此数据在调用之间流动而不被复制.


Wen*_*Qiu 9

时间似乎存在一些语义差异.

使用CallContext,当设置子线程/任务/异步方法的上下文时,即调用Task.Factory.StartNew(),Task.Run()或async方法时,会发生上下文更改.

使用AsyncLocal时,当子线程/任务/异步方法实际开始执行时,会发生上下文更改(正在调用更改通知回调).

时序差异可能很有趣,特别是如果您希望在切换上下文时克隆上下文对象.使用不同的机制可能会导致克隆不同的内容:使用CallContext,您可以在创建子线程/任务或调用async方法时克隆内容; 但是使用AsyncLocal,当子线程/任务/异步方法开始执行时克隆内容,父线程可能已经更改了上下文对象的内容.

  • 历史上(在.Net 4.6之前),我们不得不在CallContext上破解一个特殊的插槽,以便在切换调用上下文时克隆上下文数据结构。演示程序显示,使用CallContext克隆发生在调用线程上,与AsyncLocal相比,更改通知处理程序在被调用线程上调用。https://1drv.ms/u/s!AuD-2O_ZRWVijzXBVJTKbQeWCTzc (3认同)