应用程序的状态
13.1 应用程序的状态
表13.1概述了各状态对象的主要特征。
尽管它们的名称很不熟悉,但是HttpApplicationState和HttpSessionState对象是状态工具,完全与经典ASP的内在对象(诸如Application和Session)兼容。称为Application和Session的特别属性,允许以与ASP差不多一致的方法使用这些对象。
注意 本章将回顾几个与状态管理有关的不同层级的对象。我们不会详细讨论cookie,但是cookie的确适合在客户端存储少量信息。此信息与请求一起发送给服务器,并能通过响应进行操纵和重新发送。cookie是一个基于文本的由简单的键/值对组成的结构,并且它不会消耗服务器上的任何资源。例如,在电子商务应用中,cookie是存储用户偏爱的优选方法。此外,cookie有一个可配置的到期策略。cookie有两个缺点:它们的大小有限(依赖于浏览器,但绝不能大于8 KB);用户可能禁用它们。
表13.1 状态管理对象概述
|
对 象 |
生命期 |
数据可见性 |
位 置 |
|
Cache |
实现一种自动清除机制,并定期清除较不常用的内容 |
在所有的会话内是全局的 |
不支持Web farm或Web garden场景 |
|
HttpApplicationState |
第一个请求命中Web服务器时创建,并在应用程序关闭时释放 |
与Cache相同 |
与Cache相同 |
|
HttpContext |
跨越各请求的整个生命期 |
在涉及该请求的对象内是全局的 |
与Cache相同 |
|
HttpSessionState |
当用户发出第一个请求时创建,并一直延续到用户关闭该会话 |
对启动该会话的用户所发出的全部请求是全局的 |
经过配置可以在Web farm和Web garden中使用 |
|
ViewState |
表示正被生成的每个页面调用上下文 |
只限于排队等待相同页面的所有请求 |
经过配置可以在Web farm和Web garden中使用 |
HttpApplicationState对象使一个字典可用于存储一个应用程序中调用的所有的请求处理程序。在经典ASP中,只有页面可以访问应用程序状态;在ASP.NET中不再这样,其中所有的HTTP处理程序和模块都可以存储和检索应用程序的字典中的值。只有在发起应用程序的上下文中可以访问应用程序状态。
客户第一次请求特定的虚拟目录中的任何资源时创建HttpApplicationState类的一个实例。每个正在运行的应用程序保持自己的全局状态对象。最常用的应用程序状态方法方法是通过Page对象的Application属性。应用程序状态不能在Web farm或Web garden间共享。
13.1.1 HttpApplicationState类的属性
HttpApplicationState是密封的,并继承一个称为NameObjectCollectionBase的基类。实际上,HttpApplicationState是一个数据对集合,每个数据对由一个字符串键和一个对象值组成。这样的数据对既可以用键字符串方法,也可以用索引访问。基类的内部利用了一个初始容量为0的散列表,该容量根据需要自动递增。表13.2列出了HttpApplicationState类的属性。
表13.2 HttpApplicationState类的属性
|
属 性 |
描 述 |
|
AllKeys |
获取一个字符串数组,其中包含当前存储在该对象中的数据项的所有的键 |
|
Contents |
获得该对象的当前实例。但是等一下!该属性返回的内容只不过是对应用程序状态对象的一个引用,不是一个克隆。该属性是为了与ASP兼容而提供的 |
|
Count |
获得当前存储在该集合中的对象数 |
续表
|
属 性 |
描 述 |
|
Item |
索引器属性,提供对该集合中的一个元素的读/写访问。该元素既可以通过名称指定,也可以通过索引指定。该属性的访问器使用Get和Set方法实现 |
|
StaticObjects |
获得一个包含global.asax中使用<object>标签(其中的scope属性设置为Application)声明的所有对象的所有实例的集合 |
注意,静态对象和实际状态值存储在不同集合中。该静态集合的确切类型是HttpStaticObjectsCollection。
13.1.2 HttpApplicationState类的方法
HttpApplicationState类的方法集主要是一个名称/值集合的专用版典型方法。如表13.3所示,最显著的扩展要求序列化状态值访问所需的封锁机制。
表13.3 HttpApplicationState类的方法
|
方 法 |
描 述 |
|
Add |
添加一个新值到该集合中。该值作为一个对象添加到该集合中 |
|
Clear |
删除该集合中的所有对象 |
|
Get |
返回该集合中的一个项的值。该项既可以通过键指定,也可以通过索引指定 |
|
GetEnumerator |
返回一个计数器对象(enumerator object)以遍历该集合 |
|
GetKey |
获得该集合中指定位置处存储的项的字符串键 |
|
Lock |
锁定对整个集合的写入权限。任何并发调用者在调用UnLock之前都不能写入该集合对象 |
|
Remove |
删除键与指定字符串匹配的项 |
|
RemoveAll |
调用Clear |
|
RemoveAt |
删除指定位置处的项 |
|
Set |
将指定值赋给具有指定键的项。该方法是线程安全的,并且在写入完成之前一直封锁对该项的访问 |
|
UnLock |
开启对该集合的写访问 |
注意,GetEnumerator方法继承基本的集合类,因而不知道类的封锁机制。如果使用此方法逐一列举该集合,则通过调用基类NameObjectCollectionBase上的get方法之一获取每个返回值。不幸的是,由于对应用程序状态的并发访问,这个方法不知道派生的HttpApplicationState类上所需的封锁机制。因此,这样的列举方法不是线程安全的。一种更好的列举内容的方法是使用一个while语句和Get方法访问一个集合项。另外,我们可以在列举内容之前锁定集合。
13.1.3 状态同步
注意,HttpApplicationState上的所有操作都需要某种同步机制,以确保在一个应用程序中运行的多个线程安全地访问数值,而不会招致死锁和访问违规。写方法(诸如Set和Remove)以及Item属性的set访问器,在写入之前隐式地应用一个写入锁。Lock方法确保只有当前线程可以修改应用程序状态。对代码中需要保护起来不被其他线程访问的部分,Lock方法对它们应用相同的写入锁。
我们不需要用一对Lock/Unlock语句封装一个Set,Clear或Remove调用——实际上,这些方法已经是线程安全的。在这些情况下使用Lock,只会产生额外的开销,增加内部递归层次。
// This operation is thread-safe
Application["MyValue"] = 1;
如果要防止一组指令同时写入,则使用Lock:
// These operations execute atomically
Application.Lock();
int val = (int) Application["MyValue"];
if (val < 10)
Application["MyValue"] = val + 1;
Application.UnLock();
读方法(诸如Get、Item的get访问器,甚至Count)有一种内部同步机制,当它与Lock一起使用时,可以防止它们并发读写和跨线程读写:
// The reading is protected from concurrent read/writes
Application.Lock();
int val = (int) Application["MyValue"];
Application.UnLock();
始终应当一起使用Lock和UnLock。然而,如果忘了调用UnLock,导致死锁的可能性并不高,因为当请求完成或超时以后,Microsoft .NET Framework自动地撤销该锁。因此,如果要处理该异常,可以考虑使用一个finally块来清除该锁,否则在请求结束时让ASP.NET为我们清除该锁无疑会导致一些延迟。
13.1.4 应用程序状态的折衷
我们不用把全局数据写入HttpApplicationState对象,而是可以在global.asax文件内使用公共成员。和HttpApplicationState集合中的项相比,全局成员更可取,因为它是强类型的,并且不需要通过散列表访问以找到该值。另一方面,全局变量本身不被同步,因而必须用人工方法进行保护。我们必须使用语言结构来保护对这些成员的访问,例如,C#中的Lock运算符,或者Microsoft Visual Basic .NET中的SyncLock运算符。第12章曾介绍过全局成员。
1. 内存占用
无 论选择用什么方式来存储一个应用程序的全局状态,在考虑何时存储全局数据时,都要注意一些基本因素。首先,全局数据存储导致永久地占用内存。除非被代码显 式地删除,否则应用程序的全局状态中存储的任何数据,只有在应用程序关闭时才被删除。一方面,把几兆数据放入应用程序的内存中加快了访问速度;另一方面, 这么做在应用程序的整个运行过程中占用了宝贵的内存。
因此,每当我们需要全局共享的数据时,考虑使用Cache对象(下一章将对此进行深入讨论)是非常重要的。与Application和全局成员不同的是,ASP.NET中存储的数据采用自动清除机制,确保在过多的虚拟内存被消耗时删掉那些数据。此外,Cache对象还有许多其他特征,我们将在下一章再作介绍。现在只需要知道,Cache对象的引入只是为了缓和内存占用问题和代替Application对象。
2. 对数据的并发访问
由 于封锁机制,存储全局数据也有问题。为了确保并发线程访问不会导致数据的不一致,同步机制是必不可少的。但是,锁定应用程序状态很快就会成为一种性能瓶 颈,导致线程的非优化使用。应用程序的全局状态保存在内存中,而且绝不会侵入机器的边界。在多机器和多处理器环境中,应用程序的全局状态只限于在各机器或CPU上运行的单个工作进程。因此,这并非是真正意义上的全局性。最后,数据在内存中是有风险的,主要是由于进程可以能出故障,或者更简单地说,是由于ASP.NET进程回收。如果准备使用应用程序的状态特征,并打算把该应用程序部署到一个Web farm或Web garden场景中,则最好取消全局状态,而使用数据库表。至少应当把全局数据封装在一个职能的代理对象中,无论什么原因,该对象都会检查数据的存在,如果数据不存在了就重新填充它。下面给出了一个简单示例:
// Retrieve data
public object GetGlobalData(string entry)
{
object o = Application[entry];
if (o == null)
{
// TODO:: Reload the data from its source
return Application[entry];
}
return o;
}
浙公网安备 33010602011771号