在ASP.NET中,缓存有两种形式:应用程序数据的缓存和页面输出的缓存,这两种形式相互独立。

应用程序数据的缓存

  ASP.NET缓存API是以Cache对象为中心的全局数据容器,由所有会话共享。Cache对象是线程安全的容器,能自动移除无用的数据项,支持各种形式的信赖关系,还可以指定移除回调和优先级。

Cache类

  Cache类位于System.Web.Caching命名空间中,可以将Cache看作是应用程序级数据和对象的仓库。

  Cache类的实例是针对每个AppDomain而创建的,只要所处AppDomain运行,它就一直有效。应用程序的ASP.NET Cache的当前实例,可以由HttpContext对象的Cache属性,也可以由Page对象的Cache属性返回。

  尽管Cache和HttpApplicationState类的目标都是作为ASP.NET应用程序全局的数据仓库,但二者之间存在许多不同。

  1. Cache是一种线程安全的对象,不需要在访问前后显式的加锁与解锁。该对象内容代码中的所有关键部分会通过同步构造得到充分的保护。

  2. 存储在Cache中的数据不必与应用程序保持相同的生存期,我们可以通过Cache对象为其中存储的项指定缓存时间和优先级。

  我们可以对被缓存项进行配置,使其在指定的秒数逝去后过期,以便回收相应的内存空间。通过设置优先级,可以帮助Cache对象在内存空间短缺的情况下选择安全释放项的顺序。数据项可以与多种类型的信赖关联,若这种关联被打破,相应的缓存项便会失效。

Cache类的属性

  Cache类的属性见下表:

  NoAbsoluteExpiration和NoSlidingExpiration公共字段是内部常量,用于配置缓存项的过期策略。

  NoAbsoluteExpiration字段类型为DataTime,其值默认为DateTime.MaxValue返回的日期,即默认永不超时。NoSlidingExpiration字段类型为TimeSpan。其值默认为TimeSpan.Zero,表可调过期被禁用。

  Item是一种可读/写属性,可用于向缓存中添加新项。如果传入Item属性的健不存在,会创建新项。通过Item属性向缓存插入新项会应用许多默认属性设置:不会为该项指定过期策略和移除回调函数,但会被设置一个常规的优先级。因此,该项一直存在于缓存中,直到以编程方式将其移除或应用程序终止。使用Cache类的Insert方法可在添加新项时做更多的设置。

Cache类的方法

  下表列出了Cache的主要方法:

  传入Add和Insert方法的键和缓存项不能为null,可调过期时间不能超过1年,否则将抛出异常。

  不能对同一个对象同时设置可变过期策略和绝对过期策略。

  Add和Insert的工作方式基本一致,但有两点不同:如果新项的键已存在,Add执行会失败(但不会抛出异常),而Insert会改写现有的缓存项;Add只有一种签名,但Insert有多个重载方法。

内部视图

  Cache类继承于Object,实现了IEnumerable接口,它是一个内部类的包装器,而该类才是真正的数据容器。具体实现ASP.NET缓存功能的类取决的CPU的数目。如果只有一个CPU可用,则选用CacheSingle,否则选用CacheMultiple。不认选择哪个,数据项都会存储在哈希表中,每个哈希表对应一个CPU。下图表现了Cache对象的架构:

  每个哈希表被分为两个部分:公共部分和私有部分。在哈希表的公共部分中的所有数据对用户应用程序可见,而私有部分存储的是系统级数据。缓存是一种资源,ASP.NET运行库本身也会广泛使用它,但系统数据与应用程序数据完全分离,且应用程序无法访问缓存中的私有元素。

  Cache对象会将应用程序的读取和写入限制在数据存储的公共部分,缓存类内部的get和set方法接受一个标志,指示缓存项是否为公共的(默认为公共)。若调用来自Cache类,那么这些内部方法总会将该标志设置为默认值,以便选择公共元素。

  在配有多处理器的计算机上,ASP.NET工作进程与多个CPU紧密关联,每个处理器都会对应一个Cache对象。不同的缓存对象间不会进行同步。

ASP.NET缓存的使用

  每个运行当中的应用程序与一个Cache对象的实例相关联,其生存期也与应用程序相同。缓存中保存的是数据的引用,能够主动校验是否有效及是否过期。当系统内存不中时,Cache对象会自动移除某些不经常使用的缓存项。每个存储在缓存中的项会被附加一些特殊的属性,以便确定其优先级和过期策略。

新项的插入

  缓存项的特性由一些属性来刻画,它们作为Add和Insert方法的参数传入。具体来说包含这几种属性:

  1. 键(key)一种区分大小写的字符串

  2. 值(value)object类型的非空值

  3. 依赖项(dependency)CacheDependency类型对象,用于跟踪添加到缓存中的项与各依赖项(如:文件、目录、数据库表、应用程序缓存中的其他对象)的物理依赖关系。

  4. 绝对过期(absolute expiration)DateTime类型对象,代表被添加项的绝对过期时间。

  5. 可调过期(sliding expiration)TimeSpan类型对象,代表被添加项的相对过期时间。

  6. 优先级(priority)取自CacheItemPriority枚举的值,代表缓存项的优先级。

  7. 移除回调函数(removal callback)当相应的缓存项被移除时,ASP.NET Cache对象会调用该函数。

  向ASP.NET Cache对象中添加新项有三种方法:Item的set访问方法;Add方法;Insert方法。

  Insert方法最灵活,提供了以下4个重载:

public void Insert(string, object);
public void Insert(string, object, CacheDependency);
public void Insert(string, object, CacheDependency, DateTime, TimeSpan);
public void Insert(string, object, CacheDependency, DateTime, TimeSpan, CacheItemPriority, CacheItemRemovedCallback);

  Item属性的set方法实际调用了以下代码:

Insert(key, value, null, Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);

现有项的移除

  Remove方法用于移除缓存项。

  若从缓存中移除带有回调函数的项,将有一个CacheItemRemovedReason枚举值传入回调函数,用于提示执行移除操作的原因。下表列出了该枚举类型包含的值:

依赖项的跟踪

  通过Add方法或Insert方法添加到缓存中的项,可以与一组文件、目录、现有的项、数据库表或外部事件关联。新项与其缓存依赖间的关系可通过CacheDependency类的实例来维护。CacheDependency对象可用于关联单个文件或目录,也能关联多个。此外,它还能关联一组“缓存键”(存储在Cache中其他对象的键)和其他要监视的自定义依赖项(如,数据库表或外部事件)。

  CacheDependency类有很多构造函数,详见下表:

  下面的代码中,新创建的项与一个文件相关联:

CacheDependency dep = new CacheDependency(filename)
Cache.Insert(key, value, dep);

  在为CacheDepandency对象提供文件名或目录名时,需要用文件系统路径来表达。

移除回调函数的定义

  缓存项被移除后,由于应用程序不知道该项被移除,所以当它试图访问该项时,会得到null值。为解决这个问题,可以在访问某缓存项前对该项的是否存在进行检查。更好的办法是为该缓存项的移除事件注册一个回调函数,以便在该项失效时重新加载它。

  下面的代码演示了如何读取Web服务器中文件的内容,并将其缓存。该缓存项连同一个回调函数一起添加:

void Load_Click(object sender, EventArgs e)
{
AddFileContentsToCache(
"data.xml");
}
void Read_Click(object sender, EventArgs e)
{
object data = Cache["MyData"];
if(data == null)
{
contents.Text
= "[No data available]";
return;
}
contents.Text
= (string)data;
}
void AddFileContentsToCache(string fileName)
{
//读取文件到buf中
string file = Server.MapPath(fileName);
StreamReader reader
= new StreamReader(file);
string buf = reader.ReadToEnd();
reader.Close();
//将buf添加到Cache中
CreateAndCacheItem(buf, file);
contents.Text
= Cache["MyData"].ToString();
}
void CreateAndCacheItem(object buf, string file)
{
//创建回调委托
CacheItemRemovedCallback removal = new CacheItemRemoveCallback(ReloadItemRemoved);
//创建缓存依赖
CacheDependency dep = new CacheDependency(file);
//添加缓存项
Cache.Insert("MyData", buf, dep, Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.Nomal, removal);
}
void ReloadItemRemoved(string key, object value, CacheItemRemovedReason reason)
{
//检查删除缓存的原因是否因为文件被修改
if(reason == CacheItemRemovedReason.DependencyChanged)
{
//检查缓存键
if(key == "MyData")
//重新创建缓存
AddFileContentsToCache("data.xml");
}
}
void Remove_Click(object sender, EventArgs e)
{
Cache.Remove(
"MyData");
}

  如果向Cache中添加缓存项,并使其与一个不存在的文件、文件或键相关联,该项仍会以常规方式缓存,并像平常一样关联该依赖项。如果该文件、目录或键之后被创建,该依赖关系便会被打破,缓存的项也会失效。也就是说,如果依赖项实际不存在,则会以空内容来创建它。

缓存项的优先级设置

  可以为缓存项指定一个优先级,在系统资源不中时,缓存项的优先级越高,它在内存中被保留下来的机会也就越高。

  下表列出了可用的优先级:

数据的过期控制

  “可调过期”是一种相对过期策略,其思想是,对象在某个时间间隔(以TimeSpan表示)过后过期。但在这种情况下,每次访问该项都会刷新该时间间隔。如果要将缓存项的过期时间设置为10分钟,可使用以下代码:

Insert(key, value, null, Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(10), CacheItemPriority.Normal, null);

  在内部,该缓存项的过期时间是通过绝对时间实现的。绝对过期时间被设置为当前时间与指定的TimeSpan值之和。每次访问该项后,都会被绝对过期时间重置为最后访问时间与指定的时间间隔之和。

  在Cache类初始化后,它会立即采集系统内存的统计信息。随后,它会向一个计时器(timer)注册一个回调函数,使它每隔一秒被调用一次。该回调函数会周期性地更新和监视内存的统计数字,并在必要时激活“清理模块”。内存的统计结果是通过一系统Win32 API函数获取当前物理内存和虚拟内存的使用情况得到的。

  Cache对象将这些系统资源的状态分为高压(high pressure)和低压(low pressure)。每个指标对应于不同的内存占用比例。一般来讲,低压指示内存占用率在15%--40%之间,而高压在45%--65%之间。当内存压力达到警戒级别时,根据对象的优先级大小,不常用的对象会先被移除。

缓存中项的枚举

  Cache类是一种集合,其内容可以很方便地通过for...each语句进行枚举。如下所示:

private DataTable CacheToDataTable()
{
DataTable dt
= CreateDataTable();
foreach(DictionaryEntry elem in HttpContext.Current.Cache)
AddItemToTable(dt, elem);
return dt;
}

private DataTable CreateDataTable()
{
DataTable dt
= new DataTable();
dt.Columns.Add(
"Key", typeof(string));
dt.Columns.Add(
"Value", typeof(string));
}

private void AddItemToTable(DataTable dt, DictionaryEntry elem)
{
DataRow row
= dt.NewRow();
row[
"Key"] = elem.Key.ToString();
row[
"Value"] = elem.Value.ToString();
dt.Rows.Add(row);
}

  在枚举缓存中的项时,只能得到两种信息:键和值。用户无法读取给定项的优先级或过期策略。在枚举Cache对象的内容时,返回的是DictionaryEntry,这个泛化的对象没有针对具体信息的属性或方法。为获取更多信息,我们可以考虑使用反射。

缓存的清除

  .NET没有直接提供清除Cache类内容的方法,下面的代码演示了如何清除Cache:

public void Clear()
{
foreach(DictionaryEntry elem in Cache)
{
string s = elem.Key.ToString();
Cache.Remove(s);
}
}

缓存的同步

  对缓存项,不管是读取还是写入,从线程的角度来看,都是绝对安全的。Cache对象能够确保同时运行的线程不会干扰这些操作。如果需要保证对Cache对象的多个操作以原子方式执行,则另当别论。考虑以下代码:

int counter = -1;
object o = Cache["Counter"];
if(o == null)
{
counter
= RetrieveLastKnownValue();
}
else
{
counter
= (int)Cache["Counter"];
counter
++;
Cache[
"Counter"] = counter;
}

  Cache对象在这个原子操作(对计数器进行累加)的上下文中被重复访问。虽然内存访问Cache都是线程安全的,但并不能保证其他线程不会在期间介入。如果缓存项具有潜在的连接,应考虑使用一种锁定机制(如lock语句)。

posted on 2011-04-25 21:06  辛勤的代码工  阅读(968)  评论(0编辑  收藏  举报