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

avo*_*avo 4 .net c# task-parallel-library async-await

我注意到我的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"));
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

它产生这个输出:

dataXX
dataX
42
dataXX

如果我进行TestAsync非异步,它按预期工作:

static Task<int> TestAsync()
{
    CallContext.LogicalSetData("valueX", "dataX");
    Console.WriteLine(CallContext.LogicalGetData("valueX"));
    return Task.FromResult(42);
}
Run Code Online (Sandbox Code Playgroud)

输出:

dataXX
dataX
42
dataX

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

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

Ste*_*ary 9

在这种情况下,"正如所料"对于不同的人来说是不同的.:)

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

我解释的高层力学LocalCallContextasync的方法在我的博客.关键在这里:

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

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

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

  • 这是另一个"魔法",即将"async"添加到方法声明中会增加......它开始有点不可预测.也许你可以写一篇关于添加`async`现在做什么样的事情的文章? (3认同)
  • @avo脱离我的头脑......与常规同步方法不同,它们无法内联. (2认同)