考虑以下代码:
private static async Task Main(string[] args)
{
await SetValueInAsyncMethod();
PrintValue();
await SetValueInNonAsyncMethod();
PrintValue();
}
private static readonly AsyncLocal<int> asyncLocal = new AsyncLocal<int>();
private static void PrintValue([CallerMemberName] string callingMemberName = "")
{
Console.WriteLine($"{callingMemberName}: {asyncLocal.Value}");
}
private static async Task SetValueInAsyncMethod()
{
asyncLocal.Value = 1;
PrintValue();
await Task.CompletedTask;
}
private static Task SetValueInNonAsyncMethod()
{
asyncLocal.Value = 2;
PrintValue();
return Task.CompletedTask;
}
如果您在.NET 4.7.2控制台应用程序中运行此代码,您将获得以下输出:
SetValueInAsyncMethod: 1
Main: 0
SetValueInNonAsyncMethod: 2
Main: 2
我理解造成输出差异的原因是SetValueInAsyncMethod
不是一个真正的方法,而是由AsyncTaskMethodBuilder
执行的状态机,它在内部捕获了ExecutionContext
,而SetValueInNonAsyncMethod
只是一个常规方法。
但即使有这种理解,我仍然有一些问题:
- 这是一个bug/缺失的功能还是有意的设计决策?
- 在编写依赖于
AsyncLocal
的代码时,我需要担心这种行为吗?比如说,我想编写自己的TransactionScope
,通过await点传递一些环境数据。这里是否足够使用AsyncLocal
? - 在.NET中,当涉及到在“逻辑代码流”中保留值时,除了
AsyncLocal
和CallContext.LogicalGetData
/CallContext.LogicalSetData
外,还有其他替代方案吗?
AsyncLocal<T>.Value
实际上会更改向上遍历调用堆栈的第一个异步方法的值。因此,如果AAsync()
调用设置asyncLocal1.Value
的B()
,那么AAsync()
将看到该更新的值。使用IDisposable
可以为您提供显式范围,在该范围内存在AsyncLocal<T>.Value
更新,并且在可处置对象被处理时重置为其先前的值。 - Stephen Cleary