Web程序的主要部分:HttpApplication
每一个请求都将被路由到一个HttpApplication对象。HttpApplicationFactory类会为你的ASP.NET程序创建一个HttpApplication对象池,它负责加载程序和给每一个到来的请求分发HttpApplication的引用。这个HttpApplication对象池的大小可以通过machine.config里的ProcessModel节点中的MaxWorkerThreads选项配置,默认值是20。
HttpApplication对象池尽管以比较少的数目开始启动,通常是一个。但是当同时有多个请求需要处理时,池中的对象将会随之增加。而HttpApplication对象池,也将会被监控,目的是保持池中对象的数目不超过设置的最大值。当请求的数量减小时,池中的数目就会跌回一个较小的值。
对于你的Web程序而言,HttpApplication是一个外部容器,它对应到Global.asax文件里定义的类。基于标准的Web程序,它是你实际可以看到的进入HTTP运行时的第一个登录点。如果你查看Global.asax(后台代码),你就会看到这个类直接派生于HttpApplication。
public class Global : System.Web.HttpApplication
HttpApplication主要用作HTTP管道的事件控制器,因此,它的接口主要有事件组成,这些事件包括:
l BeginRequest
l AuthenticateRequest
l AuthorizeRequest
l ResolveRequestCache
l [此处创建处理程序(即与请求 URL 对应的页)。]
l AcquireRequestState
l PreRequestHandlerExecute
l [执行处理程序。]
l PostRequestHandlerExecute
l ReleaseRequestState
l [响应筛选器(如果有的话),筛选输出。]
l UpdateRequestCache
l EndRequest
这里的每一个事件都在Global.asax文件中以Application_为前缀,无实现代码的方法出现。举个例子,如Application_BeginRequest()和Application_AuthorizeRequest()。由于它们在程序中会经常用到,所以出于方便的考虑,这些事件的处理器都已经被提供了,这样你就不必再显式的创建这些事件处理器的委托了。
每一个ASP.NET Web程序运行在各自的AppDomain里,在AppDomain里同时运行着多个HttpApplication的实例,这些实例存放在ASP.NET管理的一个HttpApplication对象池里,认识到这一点,是非常重要的。这就是为什么可以同时处理多个请求,而这些请求不会互相干扰的原因。
使用列表4的代码,可以进一步了解AppDomain,线程,HttpApplication之间的关系。
列表 4: AppDomain, Threads and HttpApplication instances之间的关系

Code
private void Page_Load(object sender, System.EventArgs e)
{
// Put user code to initialize the page here
this.ApplicationId = ((HowAspNetWorks.Global)
HttpContext.Current.ApplicationInstance).ApplicationId;
this.ThreadId = AppDomain.GetCurrentThreadId();
this.DomainId =
AppDomain.CurrentDomain.FriendlyName;
this.ThreadInfo = "ThreadPool Thread: " +
Thread.CurrentThread.IsThreadPoolThread.ToString() +
"<br>Thread Apartment: " +
Thread.CurrentThread.ApartmentState.ToString();
// *** 为了可以同时看到多个请求一起到达,故意放慢速度
Thread.Sleep(3000);
}
这是样例程序的一部分,运行的结果如图5所示。为了检验结果,你应该打开两个浏览器,输入相同的地址,观察那些不同的ID的值。

附件:
您所在的用户组无法下载或查看附件图5:同时运行几个浏览器,你会很容易的看到AppDomains,application对象以及处理请求的线程之间内在的关系。当多个请求触发时,你会看到线程和application的ID在改变,而AppDomain的ID却没有发生变化。
你将会观察到AppDomain ID一直保持不变,而线程和HttpApplication的ID在请求多的时候会发生改变,尽管它们会出现重复。这是因为HttpApplications是在一个集合里面运行,下一个请求可能会再次使用同一个HttpApplication实例,所以有时候HttpApplication的ID会重复。注意,一个HttpApplication实例对象并不依赖于一个特定的线程,它们仅仅是被分配给处理当前请求的线程而已。
线程由.NET的ThreadPool提供服务,默认情况下,线程模型为多线程单元(MTA)。你可以通过在ASP.NET的页面的@Page指令里设置属性ASPCOMPAT="true"覆盖线程单元的状态。ASPCOMPAT意味着COM组件将在一个安全的环境下运行。ASPCOMPAT使用了单线程单元(STA)的线程为请求提供服务。STA线程在线程池里是单独设置的,这是因为它们需要特殊的处理方式。
实际上,这些HttpApplication对象运行在同一个AppDomain里是很重要的。这就是ASP.NET如何保证web.config的改变或者单独的ASP.NET页面得到验证可以贯穿整个AppDomain。改变web.config里的一个值,将导致AppDomain关闭并重新启动。这确保了所有的HttpApplication实例可以看到这些改变,这是因为当AppDomain重新加载的时候,来自ASP.NET的那些改变将会在AppDomain启动的时候重新读取。当AppDomain重新启动的时候任何静态的引用都将重新加载。这样,如果程序是从程序的配置文件读取的值,这些值将会被刷新。
在示例程序里可以看到这些,打开一个ApplicationPoolsAndThreads.aspx页面,注意观察AppDomain的ID。然后在web.config里做一些改动(增加一个空格,然后保存),重新加载这个页面(译注:由于缓存的影响可能会在原来的页面上刷新无效,需要先删除缓存再刷新即可),你就会看到一个新的AppDomain被创建了。
本质上,这些改变将引起Web程序重新启动。对于已经存在于处理管道的请求,将继续通过原来的管道处理。而对于那些新的请求,将被路由到新的AppDomain里。为了处理这些“挂起的请求”,在这些请求超时结束之后,ASP.NET将强制关闭AppDomain甚至某些请求还没有被处理。因此,在一个特定的时间点上,同一个HttpApplication实例在两个AppDomain里存在是有可能的,这个时间点就是旧的AppDomain正在关闭,而新的AppDomain正在启动。这两个AppDomain将继续为客户端请求提供服务,直到旧的AppDomain处理完所有的未处理的请求,然后关闭,这时候才会仅剩下新的AppDomain在运行。