【问题标题】:CallContext.LogicalGetData gets restored even where there is no asynchrony. Why?即使没有异步,CallContext.LogicalGetData 也会恢复。为什么?
【发布时间】:2015-07-14 23:47:42
【问题描述】:

我注意到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"));
            }
        }
    }
}

它产生这个输出:

数据XX 数据X 42 数据XX

如果我将 TestAsync 设为非异步,它会按预期工作:

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

输出:

数据XX 数据X 42 数据X

如果我在 TestAsync 内部有一些真正的异步,我会理解这种行为,但这里不是这种情况。我什至使用ExecutionContext.SuppressFlow,但这并没有改变任何东西。

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

【问题讨论】:

  • 非常好!

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


【解决方案1】:

在这种情况下,“如预期”对于不同的人来说是不同的。 :)

在最初的 Async CTP(没有修改任何框架代码)中,根本不支持“异步本地”类型的上下文。 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)。而“标志”是一种简化——没有实际的布尔成员或任何东西;它只是修改状态(ExecutionContextBelongsToCurrentScope 和朋友),以使任何未来的写入都将(浅)复制逻辑调用上下文。

只要使用async 方法的同步部分,相同的状态机方法 (Start) 就会调用 ExecutionContextSwitcher.Undo。这就是恢复以前的逻辑上下文的原因。

【讨论】:

  • 这是另一个“魔法”,将async 添加到方法声明中会增加......它开始有点不可预测。或许你可以写一篇文章,现在添加async 做了哪些事情?
  • 谢谢斯蒂芬。我想知道 async 同步 方法与常规同步方法还有什么不同,除了异常行为和 LocalCallContext 行为?
  • @avo 在我脑海中浮现.. 与常规同步方法不同,它们不能被内联。
  • 我相信这种行为是困扰异步调用后 HttpContext.Items 消失的原因。类似地,如果我使用 CallContext.LogicalGetData() 来存储对象,则在异步调用之后对该对象的更改将被恢复。唯一似乎正常流动的是 AsyncLocal。
  • @Brain2000:HttpContext.Items 不应受async 的影响。我建议首先验证您是否拥有the correct targetFramework set(至少 4.5),如果您仍然看到问题,您应该向 Microsoft 提出问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-19
  • 1970-01-01
  • 2015-10-18
  • 1970-01-01
  • 2014-09-26
相关资源
最近更新 更多