RX_*_*_RX 4 .net c# async-await executioncontext
考虑以下代码:
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;
}
Run Code Online (Sandbox Code Playgroud)
如果在.NET 4.7.2控制台应用程序中运行此代码,则将获得以下输出:
SetValueInAsyncMethod: 1
Main: 0
SetValueInNonAsyncMethod: 2
Main: 2
Run Code Online (Sandbox Code Playgroud)
我确实知道,输出的差异是由以下事实引起的:该事实实际上SetValueInAsyncMethod不是方法,而是由状态机执行的,AsyncTaskMethodBuilder该状态机在ExecutionContext内部捕获并且SetValueInNonAsyncMethod只是常规方法。
但是即使有了这种理解,我仍然有一些问题:
AsyncLocal?说,我想写我的TransactionScope-wannabe,尽管等待点流一些环境数据。是AsyncLocal够吗?AsyncLocal和CallContext.LogicalGetData/.NET CallContext.LogicalSetData中的其他替代方案?对我来说,这似乎是一个故意的决定。
如您所知,它SetValueInAsyncMethod被编译成一个状态机,该状态机隐式捕获了当前的ExecutionContext。当您更改AsyncLocal-variable时,该更改不会“流”回调用函数。相反,SetValueInNonAsyncMethod它不是异步的,因此不会编译为状态机。因此,不会捕获ExecutionContext,并且AsyncLocal对调用者可见的-variables 更改。
如果出于任何原因需要这样做,也可以自己捕获ExecutionContext:
private static Task SetValueInNonAsyncMethodWithEC()
{
var ec = ExecutionContext.Capture(); // Capture current context into ec
ExecutionContext.Run(ec, _ => // Use ec to run the lambda
{
asyncLocal.Value = 3;
PrintValue();
});
return Task.CompletedTask;
}
Run Code Online (Sandbox Code Playgroud)
这将输出3,而Main将输出2。
当然,简单地转换SetValueInNonAsyncMethod为异步以使编译器为您完成此操作更容易。
对于使用AsyncLocal(或CallContext.LogicalGetData就此而言)的代码,重要的是要知道,在调用的异步方法(或任何捕获的ExecutionContext)中更改值不会“回流”。但是AsyncLocal,只要不重新分配它,您当然仍然可以访问和修改它。
这是错误/缺少功能还是故意的设计决定?
这是一个故意的设计决定。具体来说,async状态机为其逻辑上下文设置“写时复制”标志。
与此相关的是,所有同步方法都属于其最接近的祖先async方法。
在编写依赖于AsyncLocal的代码时,我是否需要担心这种行为?说,我想编写我的TransactionScope-想通过等待点流一些环境数据。这里的AsyncLocal是否足够?
像这样的大多数系统都AsyncLocal<T>结合使用IDisposable清除AsyncLocal<T>值的模式。组合这些模式可确保它与同步或异步代码一起使用。AsyncLocal<T>如果使用的代码是一种async方法,它将自身正常工作;与之配合使用IDisposable可确保它同时适用于async同步方法。
.NET中的AsyncLocal和CallContext.LogicalGetData / CallContext.LogicalSetData是否还有其他替代选择,因为它可以在整个“逻辑代码流”中保留值?
没有。
| 归档时间: |
|
| 查看次数: |
190 次 |
| 最近记录: |