找回密码
 立即注册
首页 业界区 业界 细说 ASP.NET Cache 及其高级用法

细说 ASP.NET Cache 及其高级用法

遇玷 2025-5-29 16:06:16
许多做过程序性能优化的人,或者关注过程程序性能的人,应该都使用过各类缓存技术。而我今天所说的Cache是专指ASP.NET的Cache,我们可以使用HttpRuntime.Cache访问到的那个Cache,而不是其它的缓存技术。
以前我在【我心目中的Asp.net核心对象】这篇博客中简单地提过它,今天我打算为它写篇专题博客,专门来谈谈它,因为它实在是太重要了。在这篇博客中,我不仅要介绍它的一些常见用法,还将介绍它的一些高级用法。在上篇博客【在.net中读写config文件的各种方法】的结尾处,我给大家留了一个问题,今天,我将在这篇博客中给出一个我认为较为完美的答案。
本文提到的【延迟操作】方法(如:延迟合并写入数据库)属于我的经验总结,希望大家能喜欢这个思路。
Cache的基本用途

提到Cache,不得不说说它的主要功能:改善程序性能。
ASP.NET是一种动态页面技术,用ASP.NET技术做出来的网页几乎都是动态的,所谓动态是指:页面的内容会随着不同的用户或者持续更新的数据,而呈现出不同的显示结果。既然是动态的,那么这些动态的内容是从哪里来的呢?我想绝大多数网站都有自己的数据源,程序通过访问数据源获取页面所需的数据,然后根据一些业务规则的计算处理,最后变成适合页面展示的内容。
由于这种动态页面技术通常需要从数据源获取数据,并经过一些计算逻辑,最终变成一些HTML代码发给客户端显示。而这些计算过程显然也是有成本的。这些处理成本最直接可表现为影响服务器的响应速度,尤其是当数据的处理过程变得复杂以及访问量变大时,会变得比较明显。另一方面,有些数据并非时刻在发生变化,如果我们可以将一些变化不频繁的数据的最终计算结果(包括页面输出)缓存起来,就可以非常明显地提升程序的性能,缓存的最常见且最重要的用途就体现在这个方面。这也是为什么一说到性能优化时,一般都将缓存摆在第一位的原因。我今天要说到的ASP.NET Cache也是可以实现这种缓存的一种技术。不过,它还有其它的一些功能,有些是其它缓存技术所没有的。

Cache的定义

在介绍Cache的用法前,我们先来看一下Cache的定义:(说明:我忽略了一些意义不大的成员)
  1. // 实现用于 Web 应用程序的缓存。无法继承此类。
  2. public sealed class Cache : IEnumerable
  3. {
  4.     // 用于 Cache.Insert(...) 方法调用中的 absoluteExpiration 参数中以指示项从不过期。
  5.     public static readonly DateTime NoAbsoluteExpiration;
  6.     // 用作 Cache.Insert(...) 或 Cache.Add(...)
  7.     //       方法调用中的 slidingExpiration 参数,以禁用可调过期。
  8.     public static readonly TimeSpan NoSlidingExpiration;
  9.     // 获取或设置指定键处的缓存项。
  10.     public object this[string key] { get; set; }
  11.     // 将指定项添加到 System.Web.Caching.Cache 对象,该对象具有依赖项、过期和优先级策略
  12.     // 以及一个委托(可用于在从 Cache 移除插入项时通知应用程序)。
  13.     public object Add(string key, object value, CacheDependency dependencies,
  14.                         DateTime absoluteExpiration, TimeSpan slidingExpiration,
  15.                         CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback);
  16.     // 从 System.Web.Caching.Cache 对象检索指定项。
  17.     // key: 要检索的缓存项的标识符。
  18.     // 返回结果: 检索到的缓存项,未找到该键时为 null。
  19.     public object Get(string key);
  20.     public void Insert(string key, object value);
  21.     public void Insert(string key, object value, CacheDependency dependencies);
  22.     public void Insert(string key, object value, CacheDependency dependencies,
  23.                                     DateTime absoluteExpiration, TimeSpan slidingExpiration);
  24.     // 摘要:
  25.     //     向 System.Web.Caching.Cache 对象中插入对象,后者具有依赖项、过期和优先级策略
  26.     //        以及一个委托(可用于在从 Cache 移除插入项时通知应用程序)。
  27.     //
  28.     // 参数:
  29.     //   key:
  30.     //     用于引用该对象的缓存键。
  31.     //
  32.     //   value:
  33.     //     要插入缓存中的对象。
  34.     //
  35.     //   dependencies:
  36.     //     该项的文件依赖项或缓存键依赖项。当任何依赖项更改时,该对象即无效,
  37.     //            并从缓存中移除。如果没有依赖项,则此参数包含 null。
  38.     //
  39.     //   absoluteExpiration:
  40.     //     所插入对象将过期并被从缓存中移除的时间。
  41.     //        如果使用绝对过期,则 slidingExpiration 参数必须为 Cache.NoSlidingExpiration。
  42.     //
  43.     //   slidingExpiration:
  44.     //     最后一次访问所插入对象时与该对象过期时之间的时间间隔。如果该值等效于 20 分钟,
  45.     //       则对象在最后一次被访问 20 分钟之后将过期并被从缓存中移除。如果使用可调过期,则
  46.     //     absoluteExpiration 参数必须为 System.Web.Caching.Cache.NoAbsoluteExpiration。
  47.     //
  48.     //   priority:
  49.     //     该对象相对于缓存中存储的其他项的成本,由 System.Web.Caching.CacheItemPriority 枚举表示。
  50.     //       该值由缓存在退出对象时使用;具有较低成本的对象在具有较高成本的对象之前被从缓存移除。
  51.     //
  52.     //   onRemoveCallback:
  53.     //     在从缓存中移除对象时将调用的委托(如果提供)。
  54.     //            当从缓存中删除应用程序的对象时,可使用它来通知应用程序。
  55.     //
  56.     // 异常:
  57.     //   System.ArgumentException:
  58.     //     为要添加到 Cache 中的项设置 absoluteExpiration 和 slidingExpiration 参数。
  59.     //
  60.     //   System.ArgumentNullException:
  61.     //     key 或 value 参数为 null。
  62.     //
  63.     //   System.ArgumentOutOfRangeException:
  64.     //     将 slidingExpiration 参数设置为小于 TimeSpan.Zero 或大于一年的等效值。
  65.     public void Insert(string key, object value, CacheDependency dependencies,
  66.                         DateTime absoluteExpiration, TimeSpan slidingExpiration,
  67.                         CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback);
  68.     // 从应用程序的 System.Web.Caching.Cache 对象移除指定项。
  69.     public object Remove(string key);
  70.     // 将对象与依赖项策略、到期策略和优先级策略
  71.     // 以及可用来在从缓存中移除项【之前】通知应用程序的委托一起插入到 Cache 对象中。
  72.     // 注意:此方法受以下版本支持:3.5 SP1、3.0 SP1、2.0 SP1
  73.     public void Insert(string key, object value, CacheDependency dependencies,
  74.                             DateTime absoluteExpiration, TimeSpan slidingExpiration,
  75.                             CacheItemUpdateCallback onUpdateCallback);
  76. }
复制代码
ASP.NET为了方便我们访问Cache,在HttpRuntime类中加了一个静态属性Cache,这样,我们就可以在任意地方使用Cache的功能。而且,ASP.NET还给它增加了二个“快捷方式”:Page.Cache, HttpContext.Cache,我们通过这二个对象也可以访问到HttpRuntime.Cache,注意:这三者是在访问同一个对象。Page.Cache访问了HttpContext.Cache,而HttpContext.Cache又直接访问HttpRuntime.Cache
Cache常见用法

通常,我们使用Cache时,一般只有二个操作:读,写。
要从Cache中获取一个缓存项,我们可以调用Cache.Get(key)方法,要将一个对象放入缓存,我们可以调用Add, Insert方法。然而,Add, Insert方法都有许多参数,有时我们或许只是想简单地放入缓存,一切接受默认值,那么还可以调用它的默认索引器,我们来看一下这个索引器是如何工作的:
  1. public object this[string key]
  2. {
  3.     get
  4.     {
  5.         return this.Get(key);
  6.     }
  7.     set
  8.     {
  9.         this.Insert(key, value);
  10.     }
  11. }
复制代码
可以看到:读缓存,其实是在调用Get方法,而写缓存则是在调用Insert方法的最简单的那个重载版本。
注意了:Add方法也可以将一个对象放入缓存,这个方法有7个参数,而Insert也有一个签名类似的重载版本,它们有着类似的功能:将指定项添加到 System.Web.Caching.Cache 对象,该对象具有依赖项、过期和优先级策略以及一个委托(可用于在从 Cache 移除插入项时通知应用程序)。然而,它们有一点小的区别:当要加入的缓存项已经在Cache中存在时,Insert将会覆盖原有的缓存项目,而Add则不会修改原有缓存项。
也就是说:如果您希望某个缓存项目一旦放入缓存后,就不要再被修改,那么调用Add确实可以防止后来的修改操作。而调用Insert方法,则永远会覆盖已存在项(哪怕以前是调用Add加入的)。
从另一个角度看,Add的效果更像是 static readonly 的行为,而Insert的效果则像 static 的行为。
注意:我只是说【像】,事实上它们比一般的static成员有着更灵活的用法。
由于缓存项可以让我们随时访问,看起来确实有点static成员的味道,但它们有着更高级的特性,比如:缓存过期(绝对过期,滑动过期),缓存依赖(依赖文件,依赖其它缓存项),移除优先级,缓存移除前后的通知等等。后面我将会分别介绍这四大类特性。
Cache类的特点

Cache类有一个很难得的优点,用MSDN上的说话就是:
此类型是线程安全的。
为什么这是个难得的优点呢?因为在.net中,绝大多数类在实现时,都只是保证静态类型的方法是线程安全,而不考虑实例方法是线程安全。这也算是一条基本的.NET设计规范原则。
对于那些类型,MSDN通常会用这样的话来描述:
此类型的公共静态(在 Visual Basic 中为 Shared)成员是线程安全的。但不能保证任何实例成员是线程安全的。
所以,这就意味着我们可以在任何地方读写Cache都不用担心Cache的数据在多线程环境下的数据同步问题。多线程编程中,最复杂的问题就是数据的同步问题,而Cache已经为我们解决了这些问题。
不过我要提醒您:ASP.NET本身就是一个多线程的编程模型,所有的请求是由线程池的线程来处理的。通常,我们在多线程环境中为了解决数据同步问题,一般是采用锁来保证数据同步,自然地,ASP.NET也不例外,它为了解决数据的同步问题,内部也是采用了锁。
说到这里,或许有些人会想:既然只一个Cache的静态实例,那么这种锁会不会影响并发?
答案是肯定的,有锁肯定会在一定程度上影响并发,这是没有办法的事情。
然而,ASP.NET在实现Cache时,会根据CPU的个数创建多个缓存容器,尽量可能地减小冲突,以下就是Cache创建的核心过程:
[code]internal static CacheInternal Create(){    CacheInternal internal2;    int numSingleCaches = 0;    if( numSingleCaches == 0 ) {        uint numProcessCPUs = (uint)SystemInfo.GetNumProcessCPUs();        numSingleCaches = 1;        for( numProcessCPUs -= 1; numProcessCPUs > 0; numProcessCPUs = numProcessCPUs >> 1 ) {            numSingleCaches = numSingleCaches > 1 ) {        numSingleCaches = numSingleCaches
您需要登录后才可以回帖 登录 | 立即注册