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

C# AsyncLocal的语义与逻辑调用上下文有何不同?,c#,.net,async-await,.net-4.6,C#,.net,Async Await,.net 4.6,.NET 4.6引入了AsyncLocal类,用于沿着异步控制流流动环境数据。我以前使用过CallContext.LogicalGet/SetData来实现这一目的,我想知道这两者在语义上是否以及在哪些方面是不同的(除了明显的API差异,比如强类型和不依赖字符串键) 我想知道这两个词在语义上是否不同,以及在哪些方面不同 可以看出,CallContext和asynchlocal在内部都依赖ExecutionContext将内部数据存储在字典中。后者似乎为异步调用添加了另一层间接寻址CallCont

.NET 4.6引入了
AsyncLocal
类,用于沿着异步控制流流动环境数据。我以前使用过
CallContext.LogicalGet/SetData
来实现这一目的,我想知道这两者在语义上是否以及在哪些方面是不同的(除了明显的API差异,比如强类型和不依赖字符串键)

我想知道这两个词在语义上是否不同,以及在哪些方面不同

可以看出,
CallContext
asynchlocal
在内部都依赖
ExecutionContext
将内部数据存储在
字典中。后者似乎为异步调用添加了另一层间接寻址
CallContext
自.NET远程处理以来就一直存在,它是一种在异步调用之间流动数据的便捷方式,而在此之前没有真正的替代方法

我能发现的最大区别是,
asynchlocal
现在允许您在底层存储值发生更改时通过回调注册通知,无论是通过
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);
});
//AsyncLocal还提供可选通知
//当与当前线程关联的值
//更改,或者是因为它已显式更改
//通过设置Value属性,或隐式更改
//当线程遇到“等待”或其他上下文转换时。
//例如,我们可能希望
//要传达给操作系统的当前文化:
静态AsyncLocal s_currentCulture=新的AsyncLocal(
args=>
{
NativeMethods.SetThreadCulture(args.CurrentValue.LCID);
});
除此之外,一个驻留在
System.Threading
中,而另一个驻留在
System.Runtime.Remoting
中,前者将在CoreCLR中得到支持


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

语义基本相同。两者都存储在
ExecutionContext
中,并通过异步调用流动

区别在于API更改(正如您所描述的)以及为值更改注册回调的能力

从技术上讲,实现上有很大的不同,因为每次复制
CallContext
时都会克隆
CallContext(使用
CallContext.Clone
),而
asynchLocal
的数据保存在
ExecutionContext.\u localValues
字典中,只复制该引用而无需任何额外工作

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

这种差异对性能既有好处,也有坏处,具体取决于
asynchlocal
的使用位置

现在,正如Hans Passant在评论
CallContext
中提到的,最初是为远程处理而设计的,在不支持远程处理的地方(例如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


注意:VisualStudioSDK中还有一个
AsyncLocal
,它基本上是
CallContext
的包装器,它显示了这些概念的相似性:。

在计时方面似乎存在一些语义差异

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

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


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

我认为没有任何更改。对于不能依赖CallContext的项目,它是一种替代方案,因为它们以CoreCLR为目标。CallContext需要远程处理支持,在小型CLR版本中不可用。您是否可以发布一个快速的代码片段来演示这种差异?历史上(在.Net 4.6之前),我们必须在CallContext上添加一个特殊的插槽,以便在切换调用上下文时克隆上下文数据结构。演示程序显示,通过CallContext,克隆发生在调用线程上,而AsyncLocal则在被调用线程上调用更改通知处理程序。