Session的来龙去脉
当我们新建一个网站时VS20XX 生成的网站模板代码中Session就是打开。是的如果你没有关闭它Session其实是一直在工作着。您只需要在Page中用一行代码就能判断您的网站是否在使用SessionSession[key1] DateTime.Now;很简单就是写一下Session如果代码能运行不出现异常就表示您的网站是支持Session的。我们可以去web.config从全局关闭它sessionState modeOff/sessionState再运行上面的代码就能看到黄页了。换句话说当您访问Session时发生以下异常即表示您的网站(或者当前页面)是不支持Session的。这里要说明一下如果您在某个页面中访问Session时出现以上黄页也有可能是页面级别关闭了Session 。在每个aspx页的Page指令行 只要我们设置一下EnableSessionState即可这个属性有3个可选项。我创建了三个页面分别接受IDE给的默认名称。// Default.aspx % Page LanguageC# AutoEventWireuptrue CodeFileDefault.aspx.cs EnableSessionStateTrue Inherits_Default % // Default2.aspx % Page LanguageC# AutoEventWireuptrue CodeFileDefault2.aspx.cs EnableSessionStateReadOnly InheritsDefault2 % // Default3.aspx % Page LanguageC# AutoEventWireuptrue CodeFileDefault3.aspx.cs EnableSessionStateFalse InheritsDefault3 %对于Default.aspx来说EnableSessionState这个设置可以不用显式指定因为它就是默认值。页面的这个参数的默认值也可以在web.config中设置如pages enableSessionStateReadOnly以上三个设置就分别设置了三个不同的Session使用方法。下面我们再来看一下这个设置对于Session来说是如何起作用的。如果您的web.config中有如下设置compilation debugtrue那么可以在x:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\websiteName\xxxxxx\xxxxxxxx中找到这么三个aspx页面的【编译前版本】说明Asp.net的编译临时目录也可以在web.config中指定如compilation debugtrue tempDirectoryD:\Temp// Default.aspx public partial class _Default : System.Web.SessionState.IRequiresSessionState { // Default2.aspx public partial class Default2 : System.Web.SessionState.IRequiresSessionState, System.Web.SessionState.IReadOnlySessionState { // Default3.aspx public partial class Default3 {或者您也可以编译整个网站从生成的程序集去看这些类的定义也能看到以上结果。也就是说Page指令中的设置被编译器转成一些接口【标记】那么您或许有点好奇为什么搞这么几个接口它们在哪里被使用 下面我们来看看这个问题当然了也只能反编译.net framework的代码找线索了。最终发现在Application的PostMapRequestHandler事件中internal class MapHandlerExecutionStep : HttpApplication.IExecutionStep { void HttpApplication.IExecutionStep.Execute() { HttpContext context this._application.Context; HttpRequest request context.Request; // .................... 注意下面这个调用 context.Handler this._application.MapHttpHandler( context, request.RequestType, request.FilePathObject, request.PhysicalPathInternal, false); // .................... } }接着找HttpContext的Handler属性public IHttpHandler Handler { set { this._handler value; // ........................... if( this._handler ! null ) { if( this._handler is IRequiresSessionState ) { this.RequiresSessionState true; } if( this._handler is IReadOnlySessionState ) { this.ReadOnlySessionState true; } // ........................... } } }至此应该大致搞清楚了原来这二个接口也只是一个标记。我们可以看一下它们的定义public interface IRequiresSessionState { } public interface IReadOnlySessionState : IRequiresSessionState { }完全就是个空接口仅仅只是为了区分使用Session的方式而已。 可能您会想HttpContext的这二个属性RequiresSessionState, ReadOnlySessionState又是在哪里被使用的。答案就是在SessionStateModule中。 SessionStateModule就是实现Session的HttpModule 它会检查了所有请求根据HttpContext的这二个属性分别采用不同的处理方式。 大致是如下方法bool requiresSessionState this._rqContext.RequiresSessionState; // 后面会有一些针对requiresSessionState的判断 if( !requiresSessionState ) { // ....................... } this._rqReadonly this._rqContext.ReadOnlySessionState; // 后面会有一些针对this._rqReadonly的判断 if( this._rqReadonly ) { this._rqItem this._store.GetItem(this._rqContext, this._rqId, out flag2, out span, out this._rqLockId, out this._rqActionFlags); } else { this._rqItem this._store.GetItemExclusive(this._rqContext, this._rqId, out flag2, out span, out this._rqLockId, out this._rqActionFlags); // .......................... }这块的代码比较散为了对这二个参数有个权威的说明我将直接引用MSDN中的原文。会话状态由 SessionStateModule 类进行管理在请求过程中的不同时间该类调用会话状态存储提供程序在数据存储区中读写会话数据。 请求开始时SessionStateModule 实例通过调用GetItemExclusive方法或 GetItem 方法如果 EnableSessionState 页属性已设置为 ReadOnly 从数据源检索数据。请求结束时如果修改了会话状态值则 SessionStateModule 实例调用 SessionStateStoreProviderBase.SetAndReleaseItemExclusive 方法将更新的值写入会话状态存储区。上面的说法提到了锁定既然有锁定就会影响并发。我们再看看MSDN中关于并发的解释。对 ASP.NET 会话状态的访问专属于每个会话这意味着如果两个不同的用户同时发送请求则会同时授予对每个单独会话的访问。 但是如果这两个并发请求是针对同一会话的通过使用相同的 SessionID 值则第一个请求将获得对会话信息的独占访问权。 第二个请求将只在第一个请求完成之后执行。如果由于第一个请求超过了锁定超时时间而导致对会话信息的独占锁定被释放 则第二个会话也可获得访问权。如果将 Page 指令中的 EnableSessionState 值设置为 ReadOnly 则对只读会话信息的请求不会导致对会话数据的独占锁定。但是对会话数据的只读请求可能仍需等到解除由会话数据的读写请求设置的锁定。ASP.NET 应用程序是多线程的因此可支持对多个并发请求的响应。多个并发请求可能会试图访问同一会话信息。 假设有这样一种情况框架集中的多个框架全部引用同一应用程序中的 ASP.NET 网页。 框架集中每个框架的独立请求可以在 Web 服务器的不同线程上并发执行。如果每个框架的 ASP.NET 页都访问会话状态变量 则可能会有多个线程并发访问会话存储区。为避免会话存储区中的数据冲突和意外的会话状态行为 SessionStateModule 和 SessionStateStoreProviderBase 类提供了一种功能能在执行 ASP.NET 页期间以独占方式锁定特定会话的会话存储项。请注意如果 EnableSessionState 属性标记为 ReadOnly则不会对会话存储项设置锁定。 但是同一应用程序中的其他 ASP.NET 页也许可以写入会话存储区因此对存储区中只读会话数据的请求可能仍然必须等待锁定数据被释放。在对 GetItemExclusive 方法的调用中请求开始时即对会话存储数据设置锁定。请求完成后在调用 SetAndReleaseItemExclusive 方法期间释放锁定。如果 SessionStateModule 实例在调用 GetItemExclusive 或 GetItem 方法过程中遇到锁定的会话数据 则该实例每隔半秒重新请求一次该会话数据直到锁定被释放或 ExecutionTimeout 属性中指定的时间已经过去。 如果请求超时SessionStateModule 将调用 ReleaseItemExclusive 方法来释放会话存储数据然后立即请求该会话存储数据。为当前响应调用 SetAndReleaseItemExclusive 方法之前锁定的会话存储数据可能已经在单独的线程上由对 ReleaseItemExclusive 方法的调用释放。 这可能导致 SessionStateModule 实例设置和释放已经由其他会话释放和修改的会话状态存储数据。 为避免这种情况SessionStateModule 为每个请求都提供一个锁定标识符以便修改锁定的会话存储数据。 仅当数据存储区中的锁定标识符与 SessionStateModule 提供的锁定标识符匹配时会话存储数据才能修改。在权威文字面前我再解释就显得是多余的。不过通过我上面的代码分析及MSDN解释我们可以明白三点1. 它说明了为什么在Application的一系列事件中PostMapRequestHandler事件要早于AcquireRequestState事件的原因。 因为SessionStateModule要访问HttpContext.RequiresSessionState但是这个属性又要等到给HttpContext.Handler赋值后才能获取到 而HttpContext.Handler的赋值操作是在PostMapRequestHandler事件中完成的有意思吧。2. 如果你没有关闭SessionSessionStateModule就一直在工作中尤其是全采用默认设置时会对每个请求执行一系列的调用。3.使用Session时尤其是采用默认设置时会影响并发访问。回到顶部Session对并发访问的影响如果您觉得前面的文字可能不是太好理解没关系我特意做了几个实验页面请继续往下看。第一个页面主要HTML部分div bThis is Default1.aspx/b /div第一个页面后台代码部分protected void Page_Load(object sender, EventArgs e) { // 这里故意停5秒。 System.Threading.Thread.Sleep(5000); }第二个页面主要HTML部分(无后台代码)div bThis is Default2.aspx/b /div第三个页面主要HTML部分(无后台代码)div bThis is Default3.aspx/b /div现在轮到主框架页面上场了主要HTML部分iframe srcDefault1.aspx width150px/iframe iframe srcDefault2.aspx width150px/iframe iframe srcDefault3.aspx width150px/iframe h1 asp:Literal IDlabResult runatserver/asp:Literal /h1主框架页面后台代码部分public partial class _Default : System.Web.UI.Page { private static int count 0; protected void Page_Load(object sender, EventArgs e) { // 因为前面的页面都没有使用Session所以就在这里简单地使用一下了。 Session[Key1] System.Threading.Interlocked.Increment(ref count); } protected override void OnPreRender(EventArgs e) { base.OnPreRender(e); this.labResult.Text Session[Key1].ToString(); } }以上代码实在太简单我也不多说了。现在来看一下页面显示效果吧。首先看到的是这个样子5秒后所有子框架的页面才会全部加载完成。