即使没有异步,CallContext.LogicalGetData也会被恢复。 为什么?

我注意到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 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 TestAsync() { CallContext.LogicalSetData("valueX", "dataX"); Console.WriteLine(CallContext.LogicalGetData("valueX")); return Task.FromResult(42); } 

输出:

 dataXX
 DATAX
 42
 DATAX

如果我在TestAsync有一些真正的异步,我会理解这种行为,但事实并非如此。 我甚至使用ExecutionContext.SuppressFlow ,但这并没有改变任何东西。

有人可以解释为什么它这样工作?

在这种情况下,“正如所料”对于不同的人而言是不同的。 🙂

在最初的Async CTP(没有修改任何框架代码)中,根本不支持“异步 – 本地”类型的上下文。 MS修改了.NET 4.5中的LocalCallContext以添加此支持。 使用异步并发(即Task.WhenAll )时 ,旧行为(使用共享逻辑上下文) Task.WhenAll

我在博客上的async方法中解释了LocalCallContext的高级机制 。 关键在这里:

async方法启动时,它会通知其逻辑调用上下文以激活写时复制行为。

逻辑调用上下文中有一个特殊的写时复制标志,只要async方法开始执行,它就会被打开。 这是由async状态机完成的(具体来说,在当前实现中, AsyncMethodBuilderCore.Start调用ExecutionContext.EstablishCopyOnWriteScope )。 而“flag”是一种简化 – 没有实际的布尔成员或任何东西; 它只是修改状态( ExecutionContextBelongsToCurrentScope和朋友),以便将来任何写入将(浅)复制逻辑调用上下文。

只要完成async方法的同步部分,相同的状态机方法( Start )将调用ExecutionContextSwitcher.Undo 。 这就是恢复以前的逻辑环境。