架构师_程序员_码农网

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 915|回复: 2

[资料] ASP.NET 中 ThreadStatic、CallContext和HttpContext 区别

[复制链接]
发表于 2023-6-30 20:34:10 | 显示全部楼层 |阅读模式
原文:http://piers7.blogspot.com/2005/11/threadstatic-callcontext-and_02.html



摘要:
即使您认为自己知道自己在做什么,如果该值可能事先设置好,那么在 ASP.Net 应用程序中的 ThreadStatic 成员、CallContext 或 Thread Local 存储中存储任何内容也是不安全的。到Page_Load(例如在IHttpModule或页面构造函数中),但在期间或之后访问。

[更新:2008 年 8 月鉴于相当多的人继续链接到这篇文章我觉得有必要澄清一下,这种线程交换行为发生在页面生命周期中的一个非常特定的点,而不是在感觉喜欢的时候发生。我在引用杰夫·纽森的话后的措辞很不幸。除此之外,我对在有关正确处理 HttpContext 的设计讨论中多次看到这篇文章的引用感到非常满意(和受宠若惊)。我很高兴人们发现它有用。]

关于如何在 ASP.Net 中实现特定于用户的单例存在很多困惑 - 也就是说,全局数据仅对一个用户或请求而言是全局的。这并不是一个不常见的要求:将事务、安全上下文或其他“全局”数据发布在一个地方,而不是将其推送到每个方法调用中,因为流浪数据可以实现更清晰(且更具可读性)的实现。然而,如果你不小心的话,这是一个向自己的脚(或头)开枪的好地方。我以为我知道发生了什么事,但我不知道。

首选选项,将单例存储在HttpContext.Current.Items中,简单且安全,但将所讨论的单例与 ASP.Net 应用程序中的使用联系起来。如果单例在您的业务对象中出现故障,那么这并不理想。即使您将属性访问包装在if语句中


Summary:
Even if you think you know what you're doing, it is not safe to store anything in a ThreadStatic member, CallContext or Thread Local Storage within an ASP.Net application, if there is the possibilty that the value might be setup prior to Page_Load (eg in IHttpModule, or page constructor) but accessed during or after.

[Update: Aug 2008 In view of the fairly large number of people continuing to link to this post I feel the need to clarify that this thread-swapping behaviour happens at a very specific point in the page lifecycle and not whenever-it-feels-like-it. My wording after the Jef Newson quote was unfortunate. That aside, I've been immensely gratified (and flattered) by the number of times I've seen this post cited within design discussions around dealing appropriately with HttpContext. I'm glad people found it useful.]

There's a lot of confusion about using how to implement user-specific singletons in ASP.Net - that is to say global data that's only global to one user or request. This is not an uncommon requirement: publishing Transactions, security context or other 'global' data in one place, rather than pushing it through every method call as tramp data can make for a cleaner (and more readable) implementation. However its a great place to shoot yourself in the foot (or head) if you're not careful. I thought I knew what was going on, but I didn't.

The preferred option, storing your singletons in HttpContext.Current.Items, is simple and safe, but ties the singleton in question to being used within an ASP.Net application. If the singleton's down in your business objects, this isn't ideal. Even if you wrap the property-access in an if statement

... then you've still got to reference System.Web from that assembly, which tends to encorage more 'webby' objects in the wrong place.

The alternatives are to use a [ThreadStatic] static member, Thread local storage (which pretty much amounts to the same thing), or CallContext.

The problems with [ThreadStatic] are well documented, but to summarize:
Field initalizers only fire on the first thread
ThreadStatic data needs explicit cleaning up (eg in EndRequest), because whilst the Thread's reachable, the ThreadStatic data won't be GC'd so you might be leaking resources.
ThreadStatic data is only any good within a request, because the next request might come in on a different thread, and get someone else's data.
Scott Hanselman gets it right, that ThreadStatic doesn't play well with ASP.Net, but doesn't fully explain why.

Storage in CallContext alleviates some of these problems, since the context dies off at the end of the request and GC will occur eventually (though you can still leak resources until the GC happens if you're storing Disposables). Additionally CallContext is how HttpContext gets stored, so it must be ok, right?. Irrespective, you'd think (as I did) that provided you cleaned up after yourself at the end of each request, everthing would be fine:
“如果你在请求开始时初始化ThreadStatic变量,并在请求结束时正确处理引用的对象,我会冒险声称不会发生任何糟糕的事情

“现在,我可能错了。clr可能会在中途停止托管线程,在某个地方序列化它的堆栈,给它一个新的堆栈,然后让它开始执行。我对此深表怀疑。我想可以想象,超线程也会让事情变得困难,但我也对此表示怀疑。”

杰夫·纽瑟姆


"If you initialize a ThreadStatic variable at the beginning of a request, and you properly dispose of the referenced object at the end of the request, I am going to go out on a limb and claim that nothing bad will happen. You're even cool between contexts in the same AppDomain

"Now, I could be wrong on this. The clr could potentially stop a managed thread mid-stream, serialize out its stack somewhere, give it a new stack, and let it start executing. I seriously doubt it. I suppose that it is conceivable that hyperthreading makes things difficult as well, but I also doubt that."
Jef Newsom
更新:这是一个误导。我稍后会进一步解释,这种线程交换只能发生在BeginRequest和Page_Load之间,但Jef的引用创建了一个非常强大的图像,我未能立即纠正。

Update: This was the misleading bit. I do explain further later on that this thread-swap can only happen between the BeginRequest and the Page_Load, but Jef's quote creates a very powerful image I failed to immediately correct. My bad.
因此,在某个时刻,ASP.NET决定有太多的I/O线程在处理其他请求。[…]它只接受请求,并将其在ASP.NET运行时内的内部队列对象中排队。然后,在排队之后,I/O线程将请求一个工作线程,然后I/O线程将返回到其池中。[…]因此ASP.NET将让该工作线程处理该请求。它将把它带到ASP.NET运行时,就像I/O线程在低负载下一样。

So at some point ASP.NET decides that there are too many I/O threads processing other requests. [...] It just takes the request and it queues it up in this internal queue object within the ASP.NET runtime. Then, after that's queued up, the I/O thread will ask for a worker thread, and then the I/O thread will be returned to its pool. [...] So ASP.NET will have that worker thread process the request. It will take it into the ASP.NET runtime, just as the I/O thread would have under low load.

现在我一直都知道这件事,但我认为这件事发生得很早,我并不在乎。然而,我似乎错了。我们在ASP.Net应用程序中遇到了一个问题,用户在点击另一个链接后就点击了一个链接,而我们的应用程序在其中一个单例中出现了空引用异常(我对单例使用的是CallContext而不是ThreadStatic,但事实证明这无关紧要)。

我对ASP.Net的线程到底是如何工作的做了一些研究,得到了伪装成事实的相互矛盾的意见(请求在请求中是线程敏捷的,而请求在其生命周期内被固定在线程上),所以我在一个测试应用程序中用一个慢页面(睡眠一秒钟)和一个快页面复制了我的问题。我单击慢速页面的链接,在页面返回之前,我单击快速页面的链接。结果(对正在发生的事情的log4net转储)让我大吃一惊。

输出显示,对于第二个请求,HttpModule管道中的BeginRequest事件和页面构造函数在一个线程上触发,但page_Load在另一个线程中触发。第二个线程已经从第一个线程迁移了HttpContext,但没有迁移CallContext或ThreadStatic(注意:由于HttpContext本身存储在CallContext中,这意味着ASP.Net正在显式地迁移HttpContext)。让我们再说一遍:


Now I always knew about this, but I assumed it happened early enough in the process that I didn't care. It appears however that I was wrong. We've been having a problem in our ASP.Net app where the user clicks one link just after clicking another, and our app blows up with a null reference exception for one of our singletons (I'm using CallContext not ThreadStatic for the singleton, but it turns out it doesn't matter).

I did a bit of research about how exactly ASP.Net's threading works, and got conflicting opinions-masquerading-as-fact (requests are thread-agile within a request vs requests are pinned to a thread for their lifetime) so I replicated my problem in a test application with a slow page (sleeps for a second) and a fast page. I click the link for the slow page and before the page comes back I click the link for the fast page. The results (a log4net dump of what's going on) surprised me.

What the output shows is that - for the second request - the BeginRequest events in the HttpModule pipeline and the page constructor fire on one thread, but the Page_Load fires on another. The second thread has had the HttpContext migrated from the first, but not the CallContext or the ThreadStatic's (NB: since HttpContext is itself stored in CallContext, this means ASP.Net is explicitly migrating the HttpContext across). Let's just spell this out again:

  • 线程切换发生在创建IHttpHandler之后
  • 页面的字段初始化程序和构造函数运行后
  • 在Global.ASA/IHttpModules正在使用的任何BeginRequest、AuthenticateRequest、AquireSessionState类型事件之后。
  • 只有HttpContext迁移到新线程



The thread switch occurs after the IHttpHandler has been created
After the page's field initializers and constructor run
After any BeginRequest, AuthenticateRequest, AquireSessionState type events that your Global.ASA / IHttpModules are using.
Only the HttpContext migrates to the new thread

这是一个主要的PITA,因为据我所见,这意味着ASP.Net中“ThreadStatic”类行为的唯一持久性选项是使用HttpContext。因此,对于您的业务对象,要么您一直使用if(HttpContext.Current!=null)和System.Web引用(yuck),要么您必须为静态持久性想出某种提供者模型,这需要在访问这些单例之前进行设置。双重恶心。

请有人说事实并非如此。

附录:完整的日志:


This is a major PITA, because as far as I can see it mean the only persistence option for 'ThreadStatic'esque behavior in ASP.Net is to use HttpContext. So for your business objects, either you're stuck with the if(HttpContext.Current!=null) and the System.Web reference (yuck) or you've got to come up with some kind of provider model for your static persistence, which will need setting up prior to the point that any of these singletons are accessed. Double yuck.

Please someone say it ain't so.

Appendix: That log in full:
[3748] INFO  11:10:05,239 ASP.Global_asax.Application_BeginRequest() - BEGIN /ConcurrentRequestsDemo/SlowPage.aspx
[3748] INFO  11:10:05,239 ASP.Global_asax.Application_BeginRequest() - threadid=, threadhash=, threadhash(now)=97, calldata=
[3748] INFO  11:10:05,249 ASP.SlowPage_aspx..ctor() - threadid=3748, threadhash=(cctor)97, threadhash(now)=97, calldata=3748, logicalcalldata=3748
[3748] INFO  11:10:05,349 ASP.SlowPage_aspx.Page_Load() - threadid=3748, threadhash=(cctor)97, threadhash(now)=97, calldata=3748, logicalcalldata=3748
[3748] INFO  11:10:05,349 ASP.SlowPage_aspx.Page_Load() - Slow page sleeping....

[2720] INFO  11:10:05,669 ASP.Global_asax.Application_BeginRequest() - BEGIN /ConcurrentRequestsDemo/FastPage.aspx
[2720] INFO  11:10:05,679 ASP.Global_asax.Application_BeginRequest() - threadid=, threadhash=, threadhash(now)=1835, calldata=
[2720] INFO  11:10:05,679 ASP.FastPage_aspx..ctor() - threadid=2720, threadhash=(cctor)1835, threadhash(now)=1835, calldata=2720, logicalcalldata=2720, threadstatic=2720

[3748] INFO  11:10:06,350 ASP.SlowPage_aspx.Page_Load() - Slow page waking up....
[3748] INFO  11:10:06,350 ASP.SlowPage_aspx.Page_Load() - threadid=3748, threadhash=(cctor)97, threadhash(now)=97, calldata=3748, logicalcalldata=3748
[3748] INFO  11:10:06,350 ASP.Global_asax.Application_EndRequest() - threadid=3748, threadhash=97, threadhash(now)=97, calldata=3748
[3748] INFO  11:10:06,350 ASP.Global_asax.Application_EndRequest() - END /ConcurrentRequestsDemo/SlowPage.aspx

[4748] INFO  11:10:06,791 ASP.FastPage_aspx.Page_Load() - threadid=2720, threadhash=(cctor)1835, threadhash(now)=1703, calldata=, logicalcalldata=, threadstatic=
[4748] INFO  11:10:06,791 ASP.Global_asax.Application_EndRequest() - threadid=2720, threadhash=1835, threadhash(now)=1703, calldata=
[4748] INFO  11:10:06,791 ASP.Global_asax.Application_EndRequest() - END /ConcurrentRequestsDemo/FastPage.aspx
关键是当FastPage的Page_Load触发时会发生什么。ThreadID是4748,但存储在ctor的HttpContext中的ThreadID I是2720。逻辑线程的哈希代码是1703,但我存储在ctor中的哈希代码为1835。我存储在CallContext中的所有数据都不见了(即使是标记为ILogicalThreadAffinnative的数据),但HttpContext仍然存在。正如你所料,我的ThreadStatic也不见了。

The key bit is what happens when FastPage's Page_Load fires. The ThreadID is 4748, but the threadID I stored in HttpContext in the ctor is 2720. The hash code for the logical thread is 1703, but the one I stored in the ctor is 1835. All data I stored in the CallContext is gone (even that marked ILogicalThreadAffinative), but HttpContext is still there. As you'd expect, my ThreadStatic is gone too.
(完)




上一篇:.NET/C# 集合 Any() 和 Count() 到底哪个快
下一篇:【转】C# 中的 Lazy 是如何保证线程安全的
码农网,只发表在实践过程中,遇到的技术难题,不误导他人。
 楼主| 发表于 2023-6-30 20:35:23 | 显示全部楼层
HttpContext.Current.Items用途
https://www.itsvse.com/thread-4501-1-1.html
码农网,只发表在实践过程中,遇到的技术难题,不误导他人。
 楼主| 发表于 2023-7-2 09:59:06 | 显示全部楼层
CallContext

命名空间:System.Runtime.Remoting.Messaging
类型完全限定名称:System.Runtime.Remoting.Messaging.CallContext

用途:用于提供与执行代码路径一起传送的属性集,直白讲就是:提供线程(多线程/单线程)代码执行路径中数据传递的能力。
方法描述是否可用于多线程环境
SetData存储给定的对象并将其与指定名称关联。
GetData从System.Runtime.Remoting.Messaging.CallContext中检索具有指定名称的对象
LogicalSetData将给定的对象存储在逻辑调用上下文,并将其与指定名称关联。
LogicalGetData 从逻辑调用上下文中检索具有指定名称的对象。
FreeNamedDataSlot清空具有指定名称的数据槽。
HostContext 获取或设置与当前线程相关联的主机上下文。在Web环境下等于System.Web.HttpContext.Current


码农网,只发表在实践过程中,遇到的技术难题,不误导他人。
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

免责声明:
码农网所发布的一切软件、编程资料或者文章仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。

Mail To:help@itsvse.com

QQ|手机版|小黑屋|架构师 ( 鲁ICP备14021824号-2 )|网站地图

GMT+8, 2024-4-28 11:20

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表