我们如何为异步 .net 方法创建调用上下文?
Posted
技术标签:
【中文标题】我们如何为异步 .net 方法创建调用上下文?【英文标题】:How can we create a callcontext for async .net methods? 【发布时间】:2014-11-18 17:42:14 【问题描述】:在同步环境中,很容易创建一个作用域上下文,它允许您将带外上下文附加到当前线程。这方面的示例是当前的 TransactionScope 或线程静态日志记录上下文。
using (new MyContext(5))
Assert.Equal(5, MyContext.Current);
Assert.Equal(null, MyContext.Current);
使用a combination of IDisposable and a thread-static field 很容易实现上下文。
显然,这在使用异步方法时会分崩离析,因为上下文是基于线程静态字段的。所以,这失败了:
using (new MyContext(5))
Assert.Equal(5, MyContext.Current);
await DoSomethingAsync();
Assert.Equal(5, MyContext.Current);
当然,我们还希望将上下文传递给调用链中的异步方法,所以这应该也可以:
using (new MyContext(5))
Assert.Equal(5, MyContext.Current);
await AssertContextIs(5);
有人知道如何实现吗?在使用 async/await 模式时丢失带外上下文会使某些代码片段非常难看。
考虑一下您希望使用基于请求的上下文进行日志记录的异步 WebAPI 调用。您希望深入调用堆栈的记录器知道请求 ID,而无需使用参数将请求 ID 一直传递到调用堆栈。
感谢您的帮助!
【问题讨论】:
我真的不明白在你的第二个例子中什么不起作用。在await DoSomethingAsync
之后,您仍然处于using
语句的上下文中。
【参考方案1】:
最好的解决方案是在 ASP.NET 中使用 HttpContext.Current.Items
,或者在 OWIN 中使用 IOwinRequest.[Get|Set]
。是的,这确实意味着将请求对象传递到任何地方,如果您认为您的“上下文值”属于该请求,这是有道理的。如果您对了解 OWIN 的“库”方法感到不舒服,那么很容易编写一个“上下文包装器”对象并传递它。
但是,如果您确定不想传递任何东西,那么您可以use LogicalCallContext
with immutable data,正如我在我的博客中所描述的那样。如果您只关心日志记录,那么您可能会发现我的 AsyncDiagnostics library 很有用 - 它使用 PostSharp 注入方法名称,您也可以将自己的信息添加到“异步堆栈”中。
【讨论】:
逻辑调用上下文正是我想要的。谢谢!【参考方案2】:如果您在托管环境中(您正在谈论 WebAPI),您应该使用请求上下文 (HttpContext.Current),而不是线程,因为您不管理线程并且不知道何时/如果原始线程将不知道如何恢复它。
使用 await
关键字正确处理请求上下文。
【讨论】:
首先,不保证 HttpContext.Current 可用(仅适用于 IIS 托管应用程序)。所以,这是我绝对不想依赖的 System.Web 依赖项。但是,更重要的是,我的问题更笼统,请求 ID 只是一个用例的示例。我肯定在寻找问题的通用解决方案:在使用 async/await 模式时传达 OOB 上下文。 async/await 依赖于 SynchronizationContext 而不是线程。因此,解决方案是将您的数据附加到上下文中,这样您就可以依赖于您的上下文。如果您想避免它,请避免使用上下文并提供值作为参数。以上是关于我们如何为异步 .net 方法创建调用上下文?的主要内容,如果未能解决你的问题,请参考以下文章
C# EF6 使用 Unity 对一个上下文进行多次异步调用 - Asp.Net Web Api
ASP.NET Razor - 如何为对象列表创建 POST 表单?