CallContext.LogicalGetData 即使在没有异步的情况下也会恢复。为什么?
CallContext.LogicalGetData gets restored even where there is no asynchrony. Why?
我注意到 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
,但这并没有改变任何东西。
有人可以解释一下为什么会这样吗?
"As expected" 在这种情况下对于不同的人来说是不同的。 :)
在最初的 Async CTP(没有修改任何框架代码)中,根本不支持 "async-local" 种上下文。 MS 修改了 .NET 4.5 中的 LocalCallContext
以添加此支持。旧行为(具有共享逻辑上下文)是 especially problematic when working with asynchronous concurrency (i.e., Task.WhenAll
).
我在我的博客上解释 high-level mechanics of LocalCallContext
within async
methods。关键在这里:
When an async
method starts, it notifies its logical call context to activate copy-on-write behavior.
只要 async
方法开始执行,逻辑调用上下文中就有一个特殊的写时复制标志。这是由 async
状态机完成的(具体来说,在当前实现中,AsyncMethodBuilderCore.Start
调用 ExecutionContext.EstablishCopyOnWriteScope
)。 "flag" 是一种简化——没有实际的布尔成员或任何东西;它只是修改状态(ExecutionContextBelongsToCurrentScope
和朋友),任何未来的写入都会(浅)复制逻辑调用上下文。
相同的状态机方法 (Start
) 将在完成 async
方法的同步部分时调用 ExecutionContextSwitcher.Undo
。这就是恢复以前的逻辑上下文。
我注意到 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
,但这并没有改变任何东西。
有人可以解释一下为什么会这样吗?
"As expected" 在这种情况下对于不同的人来说是不同的。 :)
在最初的 Async CTP(没有修改任何框架代码)中,根本不支持 "async-local" 种上下文。 MS 修改了 .NET 4.5 中的 LocalCallContext
以添加此支持。旧行为(具有共享逻辑上下文)是 especially problematic when working with asynchronous concurrency (i.e., Task.WhenAll
).
我在我的博客上解释 high-level mechanics of LocalCallContext
within async
methods。关键在这里:
When an
async
method starts, it notifies its logical call context to activate copy-on-write behavior.
只要 async
方法开始执行,逻辑调用上下文中就有一个特殊的写时复制标志。这是由 async
状态机完成的(具体来说,在当前实现中,AsyncMethodBuilderCore.Start
调用 ExecutionContext.EstablishCopyOnWriteScope
)。 "flag" 是一种简化——没有实际的布尔成员或任何东西;它只是修改状态(ExecutionContextBelongsToCurrentScope
和朋友),任何未来的写入都会(浅)复制逻辑调用上下文。
相同的状态机方法 (Start
) 将在完成 async
方法的同步部分时调用 ExecutionContextSwitcher.Undo
。这就是恢复以前的逻辑上下文。