我们如何为异步 .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 方法创建调用上下文?的主要内容,如果未能解决你的问题,请参考以下文章

如何为 ListBoxItems 创建动态上下文菜单?

C# EF6 使用 Unity 对一个上下文进行多次异步调用 - Asp.Net Web Api

如何为贝宝上下文快速结帐创建反应组件?

ASP.NET Razor - 如何为对象列表创建 POST 表单?

如何为特定页面创建父组件(以便能够在父级中添加反应上下文)?

如何为多个数据库启用迁移,但只有一个上下文?