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
if(HttpContext.Current!=null){ /* store in HttpContext */ }else{ /* store in CallContext or ThreadStatic */ }... 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.
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:
"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
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.
Trouble is that's exactly what happens. Trouble is that's almost what happens. Under load ASP.Net can migrate inbound requests from its IO thread pool to a queue taken up by it's worker process thread pool:
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.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).
Microsoft ASP.NET Threading Webcast
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:
- 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
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
相关推荐
在ASP.NET中生成随机数是一项常见的任务,尤其是在创建动态网页、进行数据填充或者实现安全相关的功能时。ASP.NET提供了一些内置的方法和类来帮助开发者生成随机数。下面将详细介绍如何在ASP.NET中生成随机数以及...
### 优秀 ASP.NET 及 WinForm 开发人员所需掌握的核心知识点 #### 一、ASP.NET 和 WinForm 基础概念 **1.1 什么是 .NET Framework** .NET Framework 是微软开发的一个软件框架,用于创建各种类型的应用程序,包括...
在ASP.NET编程中,Unity容器是一个强大的依赖注入框架,用于实现控制反转(Inversion of Control, IoC)和依赖注入(Dependency Injection, DI)。对象生存期管理是IoC容器的核心功能之一,它涉及到对象实例的创建、...
在ASP.NET中,合理使用多线程可以提高并发处理能力。 **3.5 系统资源** 合理管理和优化系统资源(如内存、CPU等)对于提高性能至关重要。 **3.6 页面处理** 优化页面加载过程,例如通过压缩HTML、CSS和...
#### 三、ASP.NET **3.1 减少往返行程(Reduce Round Trips)** - **3.1.1 减少服务器与客户端之间的交互次数**:尽量减少HTTP请求的数量,可以通过合并文件、使用缓存等方式来实现。 **3.2 避免阻塞和长时间的...
在本文中,我们将探讨如何扩展ASP.NET MVC框架并利用StructureMap进行依赖注入,特别是关注Model层、Service层和Repository层的构建。首先,我们要明确三层架构的目的:分离关注点,提高代码可读性,降低各层间的...
3. ASP.NET优化 3.1 减少往返行程:合并请求,减少HTTP头信息。 3.2 避免阻塞和长时间的作业:异步处理,避免UI阻塞。 3.3 使用缓存:如OutputCache,减少服务器计算。 3.4 多线程:注意同步问题,避免资源竞争。 ...
在Windows Forms(WinForm)应用程序开发中,`ThreadStatic`是一个关键的概念,它与多线程编程密切相关。`ThreadStatic`是C#语言中的一个属性,用于修饰静态字段,使得这个字段在每个线程上都有独立的副本。这意味着...
### Lucene.net 全文检索知识点详解 #### 一、Lucene.net简介 Lucene.net是一种高性能、全功能的文本搜索引擎库,适用于.NET平台。它提供了丰富的API来创建索引和执行全文检索任务。该库支持多种语言和文档格式,...
5. **随机数生成**:摇奖的核心是生成随机数,.NET框架提供了Random类,但为了保证在多线程环境下的随机性,可能需要对每个线程实例化单独的Random对象,或者使用ThreadStatic特性来确保每个线程有独立的随机数种子...
2. **线程安全的实现**:为了解决多线程问题,可以使用`lock`关键字或`System.Threading.ThreadStatic`属性。例如,使用`lock`关键字实现线程安全的经典单态: ```csharp public class Singleton { private static ...
žERO分配字符串构建面向.NET的核心和统一。 构造StringBuilder以避免分配生成器本身 从ThreadStatic或ArrayPool租用写入缓冲区 所有的append方法都是泛型的( Append(T value) )并直接写入缓冲区而不是连接value...
1.3.2 使用 ThreadStatic 替代 NameDataSlot ★ 1.3.3 多线程编程技巧 1.4 类型系统 1.4.1 避免无意义的变量初始化动作 1.4.2 ValueType 和 ReferenceType 1.4.3 尽可能使用最合适的类型 1.5 异常处理 1.5.1 ...
PerThreadLifetimeManager的问题使用Unity内置的...在类似ASP.NET PerCall或WCF PerCall条件下,当Call1在线程ManagedThreadId1中处理完毕后,Call2发生,而Call2很有可能也在线程ManagedThreadId1中处理
根据给定的文件信息,以下是对.NET代码优化的总结和归纳的知识点。 .NET技术框架自2002年首次推出以来,已经经历了多个版本的更新和优化。在.NET框架中,C#语言作为主要的编程语言,拥有丰富的类库和运行时优化机制...