即使没有异步,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
。 这就是恢复以前的逻辑环境。