线程静态、ASP.NET 和异步处理程序

Posted

技术标签:

【中文标题】线程静态、ASP.NET 和异步处理程序【英文标题】:Thread Static, ASP.NET and Async handlers 【发布时间】:2014-07-04 12:45:51 【问题描述】:

请考虑以下情况:

    异步 .ashx 处理程序 异步 .asmx 网络服务方法 一种同步 MVC 5 控制器操作方法

我试图找出一种方法来设置“逻辑线程”特定数据,这些数据可以在“逻辑”http请求期间一致地访问,即如果数据是在“BeginExecute”部分的线程上设置的 -任何你会考虑的异步处理程序,即使 ASP.NET 在不同的 OS/.Net 线程上执行“EndExecute”部分,该数据也可以在该 asnc 处理程序的“EndExecute”部分中使用。

此外,如果将第二个请求分配给先前分配给第一个线程的线程,我希望它所在的任何 OS/.Net 线程上的“BeginExecute”部分中的数据集在后续 http 请求中不可用http 请求位于“BeginExecute”部分,但该线程在第一个 http 请求进入其异步操作时被释放(并且它可能仍在完成其异步操作)。

我相信 .Net 中的“逻辑线程”或“逻辑线程上下文”这个词实际上意味着我提到的相同的“逻辑”操作流程(而不是不断被重新分配的底层 OS/.Net 线程)。如果从工作流的角度来看,每个 http 请求都是一个新的“逻辑”操作(即使多个用户按顺序或并行调用同一个 Web 服务,每个请求都是一个新的独立逻辑操作),并且在这个意思是,“逻辑”操作是一次性的,不能重复。然而,相同的底层 OS/.Net 线程可以在到达时根据其可用性映射到“逻辑”操作。

此外,我想将此数据公开为 HttpContext.Current 类型的静态属性。对某些人来说,这可能会让您感到意外,但如果您使用例如异步 .asmx Web 服务方法,HttpContext.Current 将无法正常工作。我确定我已经阅读了网络上的内容,其中说 HttpContext.Current 应该始终返回正确的 HttpContext,但我在 .asmx web-methods 的 EndExecuteMethod 中看到它为空。如果 somecan 可以确认我的最后一个陈述是否正确,那就太好了,但这个陈述并不是我想在这里提出的总体问题。

在阅读了大量文献(例如What is the difference between log4net.ThreadContext and log4net.LogicalThreadContext?、http://msmvps.com/blogs/jon_skeet/archive/2010/11/08/the-importance-of-context-and-a-question-of-explicitness.aspx、http://blog.stephencleary.com/2013/04/implicit-async-context-asynclocal.html 以及更多包括 MSDN 文档)之后,我的推论如下:

    ThreadStatic 是底层 OS/.Net 线程的本地线程,而不是“逻辑”操作,因此在我的示例中;如果第二个 http 请求被分配与第一个线程的“BeginExecute”相同的线程,则“BeginExecute”中第一个 http 请求的数据集将在下一个 http 请求中可见。如果这些数据恰好被 .Net 重新分配给另一个线程(在绝大多数情况下都会发生),那么这些数据在“EndExecute”中将不可用。 Thread.SetData 对我的用例来说更成问题。它需要传入数据槽,如果我要从 Thread.GetNamedDataSlot 的返回值传入数据槽,则该信息在整个应用程序域中都可用;因为命名数据槽在线程之间共享。 CallContext.SetData 类似于 ThreadStatic(这意味着它不被应用程序域共享,但如果不同的 http 请求被分配到相同的底层 OS/.Net 线程,它们将看到相同的数据)。 CallContext.SetData 提供了一种额外的能力来编组 RPC 调用的上下文数据,这与当前提出的问题无关。 然后是 ThreadLocal 类 (.Net 4/.Net 4.5)。它似乎可以解决我的问题的一部分,我可以在 BeingExecute 操作的 stateObject 中传递它,并从 endExecute 操作的相同 stateObject 参数中提取。从这个角度来看,ThreadLocal 似乎是为.Net 的异步支持而编写的。但是当我需要像 HttpContext.Current 一样访问它时,它就不起作用了,因为我看不到保留它的“逻辑线程静态”实例(除非我在前面的 3 点中说错了)。 最后,CallContext.LogicalSetData 似乎实现了我想要实现的目标。使用 CallContext.LogicalSetData 和 CallContext.LogicalGetData 方法集,我应该能够实现类似 HttpContext.Current 的影响,该影响适用于“逻辑任务执行”。

现在问题来了:

    我上面所说的一切是否正确。请更正我提出的所有错误声明。 我错过了 .Net 中的线程静态类型功能是否还有其他可用选项。 CallContext.LogicalSetData/LogicalGetData 是否将上下文数据传递给 RPC 调用(msdn 页面没有明确提及,http://msdn.microsoft.com/en-us/library/system.runtime.remoting.messaging.callcontext.logicalsetdata(v=vs.110).aspx)。 使用 CallContext.LogicalSetData/LogicalGetData 是否有任何缺点(性能方面或其他方面)。 本页介绍了 LogicalSetData 的写时复制行为:http://blog.stephencleary.com/2013/04/implicit-async-context-asynclocal.html。在异步处理程序/异步 MVC 5 操作方法的上下文中,如果我使用logicalsetdata 保存引用类型并稍后更改引用类型的状态会有什么影响。有什么反响。 对于mutation/logicalsetdata/async,我仍然无法通过对对象进行变异来查看问题所在。当异步方法启动时,写时复制行为将在下次调用logicalsetdata时触发上下文数据的副本。这是一个浅拷贝,因此我的引用对象现在实际上由 2 个逻辑上下文共享,并且一个上下文中的更改在另一个上下文中可见,这是我通常期望的引用类型。

一个很长的问题,有很多参考资料,但希望我的研究做得很好,并且答案也会对其他人有所帮助。

【问题讨论】:

【参考方案1】:

我正在尝试找出一种方法来设置“逻辑线程”特定数据,这些数据可以在“逻辑”http 请求期间始终如一地访问

唯一可能的选项是HttpContext.Current.Items 和逻辑CallContext

此外,我希望它所在的任何 OS/.Net 线程上的“BeginExecute”部分中的数据集在后续的 http 请求中都不可用

HttpContext.Current.Items 将始终在新请求中被清除,但您必须自己清除逻辑 CallContext 数据。

如果您使用例如异步 .asmx Web 服务方法,HttpContext.Current 将无法正常工作。

我觉得这很令人惊讶。我还没有尝试过,但它应该可以工作 - 如果您在 .NET 4.5 上运行,目标是 .NET 4.5(即,在您的 web.config 中将 targetFramework 设置为 4.5) ,并且没有使用async void

[ThreadStatic]、线程本地数据槽、(非逻辑)CallContextThreadLocal 都是线程特定的数据,不适用于异步代码。

我上面所说的一切是否正确。请更正我提出的所有错误声明。

您的问题中的文字确实太多了。 Stack Overflow 是一个问答网站,而不是指导网站。

我错过了.Net 中的线程静态类型功能是否还有其他可用选项。

没有。

CallContext.LogicalSetData/LogicalGetData 是否将上下文数据传递给 RPC 调用

我不知道。试试看。

使用 CallContext.LogicalSetData/LogicalGetData 是否有任何缺点(性能方面或其他方面)。

肯定会影响性能。 .NET 框架针对常见情况(无逻辑调用上下文数据)进行了高度优化。

如果我使用 logicalsetdata 保存引用类型并稍后更改引用类型的状态会有什么影响。

逻辑 CallContext 具有写入时浅复制行为。因此,任何类型的异步 fork/join 并发(即Task.WhenAll)最终都会共享该状态。如果你使用ConfigureAwait(false),你也可能会遇到竞争条件。

要真正解决你的问题,我建议你先看看为什么HttpContext.Current 不能按预期工作;我的猜测(没有看到项目)是targetFramework 设置为4.0 而不是4.5HttpContext.Current.Items 是最好的选择,如果你能让它工作的话。

【讨论】:

嗨,斯蒂芬,感谢您的帮助。我不尝试使用 HttpContext.Current 的原因之一是它不适合单元测试。我认为我可以使用 ThreadStatic 或 Logical 调用上下文进行管理,以确保在每个新请求上清除先前请求中的数据。 恕我直言,我还想反驳“指导网站”的观点。在问之前,我试着研究得足够好。当然这个问题很长,我可以把它分成多个问题,每个可用选项一个。但我相信这确实是一个问题,而不是试图让某人为我找到答案。对于提出的大多数问题,我无法在网上找到直接答案。而编写测试用例来验证其中一些点是行不通的,其中一些需要在理论层面上进行确认,并且无法在测试用例中捕获。 试图理解反映的代码也无济于事,因为内部(未记录的)类、工厂模式和令人讨厌的地方,你不能总是来回理解它们之间的关系课程和工作流程。虽然我想再次感谢你,但你的回复清除了我的大部分观点。

以上是关于线程静态、ASP.NET 和异步处理程序的主要内容,如果未能解决你的问题,请参考以下文章

ASP.NET MVC中使用异步控制器

异步Action之AsyncController

为啥我的异步 ASP.NET Web API 控制器阻塞了主线程?

ASP.NET 异步/等待第 2 部分

ASP.NET 服务器不异步处理页面

Asp.net C# 静态方法线程安全错误处理