AsyncLocal 的语义与逻辑调用上下文有何不同?
How do the semantics of AsyncLocal differ from the logical call context?
.NET 4.6 引入了 AsyncLocal<T>
class 用于沿着异步控制流流动环境数据。我以前为此目的使用过 CallContext.LogicalGet/SetData
,我想知道这两者在语义上是否以及以何种方式不同(除了明显的 API 差异,如强类型和不依赖字符串键) .
I'm wondering if and in what ways the two are semantically different
从中可以看出,CallContext
和 AsyncLocal
内部都依赖 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);
});
除此之外,一个位于 System.Threading
而另一个位于 System.Runtime.Remoting
,CoreCLR 将支持前者。
此外,AsyncLocal
似乎没有 SetLogicalData
具有的浅层写时复制语义,因此数据在调用之间流动而不会被复制。
语义几乎相同。两者都存储在 ExecutionContext
中并通过异步调用流动。
不同之处在于 API 更改(正如您所描述的)以及为值更改注册回调的能力。
从技术上讲,实现上有很大的不同,因为每次复制 CallContext
时都会克隆它(使用 CallContext.Clone
),而 AsyncLocal
的数据保存在ExecutionContext._localValues
字典,仅复制该参考文献,无需任何额外工作。
为确保更新仅在您更改 AsyncLocal
的值时影响当前流,将创建一个新字典并将所有现有值浅复制到新字典。
根据使用 AsyncLocal
的位置,这种差异对性能可能有利有弊。
现在,正如 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
注意:Visual Studio SDK 中还有一个 AsyncLocal
,它基本上是 CallContext
的包装器,显示了概念的相似程度:Microsoft.VisualStudio.Threading.
时间上似乎有一些语义差异。
使用 CallContext 时,上下文更改会在子 thread/task/async 方法的上下文设置时发生,即当调用 Task.Factory.StartNew()、Task.Run() 或异步方法时。
使用 AsyncLocal,当子 thread/task/async 方法实际开始执行时,上下文更改(调用更改通知回调)发生。
时间差异可能很有趣,特别是如果您希望在切换上下文时克隆上下文对象。使用不同的机制可能会导致克隆不同的内容:使用 CallContext,您可以在创建子 thread/task 或调用异步方法时克隆内容;但是使用 AsyncLocal,当子 thread/task/async 方法开始执行时克隆内容,上下文对象的内容可能已被父线程更改。
.NET 4.6 引入了 AsyncLocal<T>
class 用于沿着异步控制流流动环境数据。我以前为此目的使用过 CallContext.LogicalGet/SetData
,我想知道这两者在语义上是否以及以何种方式不同(除了明显的 API 差异,如强类型和不依赖字符串键) .
I'm wondering if and in what ways the two are semantically different
从中可以看出,CallContext
和 AsyncLocal
内部都依赖 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);
});
除此之外,一个位于 System.Threading
而另一个位于 System.Runtime.Remoting
,CoreCLR 将支持前者。
此外,AsyncLocal
似乎没有 SetLogicalData
具有的浅层写时复制语义,因此数据在调用之间流动而不会被复制。
语义几乎相同。两者都存储在 ExecutionContext
中并通过异步调用流动。
不同之处在于 API 更改(正如您所描述的)以及为值更改注册回调的能力。
从技术上讲,实现上有很大的不同,因为每次复制 CallContext
时都会克隆它(使用 CallContext.Clone
),而 AsyncLocal
的数据保存在ExecutionContext._localValues
字典,仅复制该参考文献,无需任何额外工作。
为确保更新仅在您更改 AsyncLocal
的值时影响当前流,将创建一个新字典并将所有现有值浅复制到新字典。
根据使用 AsyncLocal
的位置,这种差异对性能可能有利有弊。
现在,正如 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
注意:Visual Studio SDK 中还有一个 AsyncLocal
,它基本上是 CallContext
的包装器,显示了概念的相似程度:Microsoft.VisualStudio.Threading.
时间上似乎有一些语义差异。
使用 CallContext 时,上下文更改会在子 thread/task/async 方法的上下文设置时发生,即当调用 Task.Factory.StartNew()、Task.Run() 或异步方法时。
使用 AsyncLocal,当子 thread/task/async 方法实际开始执行时,上下文更改(调用更改通知回调)发生。
时间差异可能很有趣,特别是如果您希望在切换上下文时克隆上下文对象。使用不同的机制可能会导致克隆不同的内容:使用 CallContext,您可以在创建子 thread/task 或调用异步方法时克隆内容;但是使用 AsyncLocal,当子 thread/task/async 方法开始执行时克隆内容,上下文对象的内容可能已被父线程更改。