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

时间:2015-07-14 13:46:52

标签: c# .net async-await task-parallel-library

我注意到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,但这并没有改变任何事情。

有人可以解释一下为什么会这样吗?

1 个答案:

答案 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。这就是恢复以前的逻辑环境。