我注意到CallContext.LogicalSetData/LogicalGetData
没有像我预期的那样工作。即使没有异步或任何类型的线程切换,async
方法中设置的值也会恢复。
这是一个简单的例子:
using System;
using System.Runtime.Remoting.Messaging;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication
{
class Program
{
static async Task<int> TestAsync()
{
CallContext.LogicalSetData("valueX", "dataX");
// commented out on purpose
// await Task.FromResult(0);
Console.WriteLine(CallContext.LogicalGetData("valueX"));
return 42;
}
static void Main(string[] args)
{
using(ExecutionContext.SuppressFlow())
{
CallContext.LogicalSetData("valueX", "dataXX");
Console.WriteLine(CallContext.LogicalGetData("valueX"));
Console.WriteLine(TestAsync().Result);
Console.WriteLine(CallContext.LogicalGetData("valueX"));
}
}
}
}
它产生这个输出:
dataXX dataX 42 dataXX
如果我使TestAsync
非异步,它会按预期工作:
static Task<int> TestAsync()
{
CallContext.LogicalSetData("valueX", "dataX");
Console.WriteLine(CallContext.LogicalGetData("valueX"));
return Task.FromResult(42);
}
输出:
dataXX dataX 42 dataX
如果我在TestAsync
内部有一些真正的异步,我会理解这种行为,但事实并非如此。我甚至使用ExecutionContext.SuppressFlow
,但这并没有改变任何事情。
有人可以解释一下为什么会这样吗?
答案 0 :(得分:9)
&#34;正如所料&#34;在这种情况下,不同的人是不同的。 :)
在最初的Async CTP(没有修改任何框架代码)中,没有支持&#34; async-local&#34;一种上下文。 MS修改了.NET 4.5中的LocalCallContext
以添加此支持。旧行为(具有共享逻辑上下文)为especially problematic when working with asynchronous concurrency (i.e., Task.WhenAll
)。
我在博客上解释了high-level mechanics of LocalCallContext
within async
methods。关键在于:
当
async
方法启动时,它会通知其逻辑调用上下文以激活写时复制行为。
在逻辑调用上下文中有一个特殊的写时复制标志,只要async
方法开始执行,它就会被打开。这是由async
状态机完成的(具体来说,在当前实现中,AsyncMethodBuilderCore.Start
调用ExecutionContext.EstablishCopyOnWriteScope
)。 &#34; flag&#34;是一种简化 - 没有实际的布尔成员或任何东西;它只是修改状态(ExecutionContextBelongsToCurrentScope
和朋友),以便将来任何写入(浅)复制逻辑调用上下文。
相同的状态机方法(Start
)只要完成ExecutionContextSwitcher.Undo
方法的同步部分就会调用async
。这就是恢复以前的逻辑环境。