.NET 4.6引入了AsyncLocal<T>
类,用于沿着异步控制流流动环境数据。我之前使用CallContext.LogicalGet/SetData
就是为了这个目的,我想知道两者在语义上是否以及在哪些方面有所不同(除了明显的API差异,如强类型和缺乏对字符串键的依赖)
答案 0 :(得分:43)
语义几乎相同。两者都存储在ExecutionContext
中,并通过异步调用。
差异是API更改(正如您所描述的)以及为值更改注册回调的功能。
从技术上讲,实施方面存在很大差异,因为CallContext
每次复制时都会被克隆(使用CallContext.Clone
)而AsyncLocal
的数据保存在ExecutionContext._localValues
中1}}字典,只是该引用被复制而无需任何额外的工作。
要确保在更改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
注意:Visual Studio SDK中还有一个AsyncLocal
,它基本上是CallContext
的包装器,它显示了概念的相似程度:Microsoft.VisualStudio.Threading。
答案 1 :(得分:19)
我想知道两者在语义上是否以及以何种方式不同
从可以看出,CallContext
和AsyncLocal
内部都依赖ExecutionContext
将其内部数据存储在Dictionary
中。后者似乎为异步调用添加了另一个间接层。自从.NET Remoting以来,CallContext
一直存在,并且是一种在异步调用之间流动数据的便捷方式,直到现在还没有真正的替代方案。
我能发现的最大区别是,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);
});
除此之外,其中一个居住在System.Threading
,而另一个居住在System.Runtime.Remoting
,前者将在CoreCLR中得到支持。
此外,AsyncLocal
似乎没有浅写入复制语义SetLogicalData
,因此数据在调用之间流动而不会被复制。
答案 2 :(得分:6)
时间上似乎存在一些语义差异。
使用CallContext,当设置子线程/任务/异步方法的上下文时,即调用Task.Factory.StartNew(),Task.Run()或async方法时,会发生上下文更改。
使用AsyncLocal,当子线程/任务/异步方法实际开始执行时,会发生上下文更改(调用更改通知回调)。
时序差异可能很有趣,特别是如果您希望在切换上下文时克隆上下文对象。使用不同的机制可能会导致克隆不同的内容:使用CallContext,您可以在创建子线程/任务或调用async方法时克隆内容;但是使用AsyncLocal,当子线程/任务/异步方法开始执行时克隆内容,父线程可能已经更改了上下文对象的内容。