Ajax[转]细说 ASP.NET Cache 及其高级用法

本文转自:http://www.cnblogs.com/fish-li/archive/2011/12/27/2304063.html

开卷目录

  • 开始
  • Cache的主干用
  • Cache的定义
  • Cache常见用法
  • Cache类的表征
  • 缓存项的晚点时
  • 缓存项的依赖性关系 –
    依赖其它缓存项
  • 缓存项的靠关系 –
    文件指
  • 缓存项的移除优先级
  • 缓存项的移除通知
  • 恰巧用缓存项的移除通知
    实现【延迟操作】
  • 凑巧用缓存项的移除通知
    实现【自动加载配置文件】
  • 文本监视技术的选取
  • 各种缓存方案的并存

森举行过程序性能优化的人头,或者关注过程程序性能的人口,应该还下了号缓存技术。
而己今天所说的Cache是专指ASP.NET的Cache,我们得以应用HttpRuntime.Cache访问到的酷Cache,而无是另的休养存技术。

早先自己在【我衷心中之Asp.net核心目标】
这首博客中简易地领到了她,今天自打算为其写首专题博客,专门来谈谈它,因为它们其实是最为重要了。在马上篇博客中,
我非但要介绍她的局部大面积用法,还将介绍其的片段高等用法。
在上篇博客【在.net中读写config文件的各种方法】
的结尾处,我于大家留下了一个题材,今天,我拿当及时首博客中让有一个己看较为圆满的答案。

正文提到的【延迟操作】方法(如:延迟合并写副数据库)属于自之经验总结,希望大家能好这思路。

回去顶部

Cache的骨干用

涉Cache,不得不说说它们的机要功用:改善程序性能。
ASP.NET是千篇一律种动态页面技术,用ASP.NET技术做出来的网页几乎都是动态的,所谓动态是依靠:页面的内容会趁不同之用户要持续创新的多寡,
而呈现出不同之显得结果。既然是动态的,那么这些动态的内容是于哪来之也?我想大部分网站还起投机之数据源,
程序通过顾数据源获取页面所需要的数额,然后根据部分作业规则之计处理,最后成为适合页面显示的情。

出于这种动态页面技术一般用打数据源获取数据,并通过一些计算逻辑,最终成为一些HTML代码发给客户端展示。而这些计算过程不言而喻也是生资金的。
这些处理成本不过直接而呈现也影响服务器的响应速度,尤其是当数码的处理过程变得复杂和访问量变大时,会变换得比较显然。
另一方面,有些数据并非时刻以发生变化,如果我们可用片转不频繁的数目的尾声计算结果(包括页面输出)缓存起来,
就得很鲜明地升级程序的习性,缓存的极其常见且极其重点之用处就是反映于这个点。
这为是干什么同样说交性优化时,一般还拿缓存摆在首先位之由来。
我今天而说交的ASP.NET Cache也是可以兑现这种缓存的均等种植技术。
不过,它还发出另的片段效能,有些是任何缓存技术所没有的。

 

回来顶部

Cache的定义

于介绍Cache的用法前,我们先来拘禁一下Cache的定义:(说明:我忽略了部分含义不坏的成员)
Ajax 1😉

// 实现用于 Web 应用程序的缓存。无法继承此类。
public sealed class Cache : IEnumerable
{
    // 用于 Cache.Insert(...) 方法调用中的 absoluteExpiration 参数中以指示项从不过期。
    public static readonly DateTime NoAbsoluteExpiration;

    // 用作 Cache.Insert(...) 或 Cache.Add(...)
    //       方法调用中的 slidingExpiration 参数,以禁用可调过期。
    public static readonly TimeSpan NoSlidingExpiration;


    // 获取或设置指定键处的缓存项。
    public object this[string key] { get; set; }


    // 将指定项添加到 System.Web.Caching.Cache 对象,该对象具有依赖项、过期和优先级策略
    // 以及一个委托(可用于在从 Cache 移除插入项时通知应用程序)。
    public object Add(string key, object value, CacheDependency dependencies,
                        DateTime absoluteExpiration, TimeSpan slidingExpiration,
                        CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback);


    // 从 System.Web.Caching.Cache 对象检索指定项。
    // key: 要检索的缓存项的标识符。
    // 返回结果: 检索到的缓存项,未找到该键时为 null。
    public object Get(string key);


    public void Insert(string key, object value);
    public void Insert(string key, object value, CacheDependency dependencies);
    public void Insert(string key, object value, CacheDependency dependencies,
                                    DateTime absoluteExpiration, TimeSpan slidingExpiration);

    // 摘要:
    //     向 System.Web.Caching.Cache 对象中插入对象,后者具有依赖项、过期和优先级策略
    //        以及一个委托(可用于在从 Cache 移除插入项时通知应用程序)。
    //
    // 参数:
    //   key:
    //     用于引用该对象的缓存键。
    //
    //   value:
    //     要插入缓存中的对象。
    //
    //   dependencies:
    //     该项的文件依赖项或缓存键依赖项。当任何依赖项更改时,该对象即无效,
    //            并从缓存中移除。如果没有依赖项,则此参数包含 null。
    //
    //   absoluteExpiration:
    //     所插入对象将过期并被从缓存中移除的时间。
    //        如果使用绝对过期,则 slidingExpiration 参数必须为 Cache.NoSlidingExpiration。
    //
    //   slidingExpiration:
    //     最后一次访问所插入对象时与该对象过期时之间的时间间隔。如果该值等效于 20 分钟,
    //       则对象在最后一次被访问 20 分钟之后将过期并被从缓存中移除。如果使用可调过期,则
    //     absoluteExpiration 参数必须为 System.Web.Caching.Cache.NoAbsoluteExpiration。
    //
    //   priority:
    //     该对象相对于缓存中存储的其他项的成本,由 System.Web.Caching.CacheItemPriority 枚举表示。
    //       该值由缓存在退出对象时使用;具有较低成本的对象在具有较高成本的对象之前被从缓存移除。
    //
    //   onRemoveCallback:
    //     在从缓存中移除对象时将调用的委托(如果提供)。
    //            当从缓存中删除应用程序的对象时,可使用它来通知应用程序。
    //
    // 异常:
    //   System.ArgumentException:
    //     为要添加到 Cache 中的项设置 absoluteExpiration 和 slidingExpiration 参数。
    //
    //   System.ArgumentNullException:
    //     key 或 value 参数为 null。
    //
    //   System.ArgumentOutOfRangeException:
    //     将 slidingExpiration 参数设置为小于 TimeSpan.Zero 或大于一年的等效值。
    public void Insert(string key, object value, CacheDependency dependencies,
                        DateTime absoluteExpiration, TimeSpan slidingExpiration,
                        CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback);

    // 从应用程序的 System.Web.Caching.Cache 对象移除指定项。
    public object Remove(string key);

    // 将对象与依赖项策略、到期策略和优先级策略
    // 以及可用来在从缓存中移除项【之前】通知应用程序的委托一起插入到 Cache 对象中。
    // 注意:此方法受以下版本支持:3.5 SP1、3.0 SP1、2.0 SP1
    public void Insert(string key, object value, CacheDependency dependencies,
                            DateTime absoluteExpiration, TimeSpan slidingExpiration,
                            CacheItemUpdateCallback onUpdateCallback);
}

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方法还发生众多参数,有时我们或许只是想大概地放入缓存,一切接受默认值,那么还得调用它的默认索引器,
我们来拘禁一下之索引器是什么样行事之:

public object this[string key]
{
    get
    {
        return this.Get(key);
    }
    set
    {
        this.Insert(key, value);
    }
}

得视:读缓存,其实是当调用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创建的中坚过程:Ajax 2😉

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;
        }
    }
    CacheCommon cacheCommon = new CacheCommon();
    if( numSingleCaches == 1 ) {
        internal2 = new CacheSingle(cacheCommon, null, 0);
    }
    else {
        internal2 = new CacheMultiple(cacheCommon, numSingleCaches);
    }
    cacheCommon.SetCacheInternal(internal2);
    cacheCommon.ResetFromConfigSettings();
    return internal2;
}

证实:CacheInternal是只里面用之包装类,Cache的群操作都使出于它们来形成。

于上头的代码中,numSingleCaches的精打细算过程格外重点,如果上面代码不轻掌握,那么要看本身下的以身作则代码:
Ajax 3😉

static void Main()
{
    for( uint i = 1; i <= 20; i++ )
        ShowCount(i);            
}
static void ShowCount(uint numProcessCPUs)
{
    int numSingleCaches = 1;
    for( numProcessCPUs -= 1; numProcessCPUs > 0; numProcessCPUs = numProcessCPUs >> 1 ) {
        numSingleCaches = numSingleCaches << 1;
    }
    Console.Write(numSingleCaches + ",");
}

程序用会输出:

1,2,4,4,8,8,8,8,16,16,16,16,16,16,16,16,32,32,32,32

CacheMultiple的构造函数如下:Ajax 4😉

internal CacheMultiple(CacheCommon cacheCommon, int numSingleCaches) : base(cacheCommon)
{
    this._cacheIndexMask = numSingleCaches - 1;
    this._caches = new CacheSingle[numSingleCaches];
    for (int i = 0; i < numSingleCaches; i++)
    {
        this._caches[i] = new CacheSingle(cacheCommon, this, i);
    }
}

现在公应该理解了咔嚓:CacheSingle其实是ASP.NET内部以的休养生息存容器,多单CPU时,它见面创造多只缓存容器。
在写入时,它是怎么样稳定这些器皿的也?请继续羁押代码:Ajax 5😉

internal CacheSingle GetCacheSingle(int hashCode)
{
    hashCode = Math.Abs(hashCode);
    int index = hashCode & this._cacheIndexMask;
    return this._caches[index];
}

说明:参数中之hashCode是直接调用我们污染的key.GetHashCode()
,GetHashCode是由Object类定义之。

就此,从之角度看,虽然ASP.NET的Cache只出一个HttpRuntime.Cache静态成员,但它的其中却可能会见含有多只缓存容器,
这种设计好当大势所趋水平及减少并发的影响。

不论是什么统筹,在差不多线程环境下,共用一个器皿,冲突是勿不了底。如果你只是希望简单的复苏存一些数据,
不欲Cache的不在少数高档特性,那么,可以考虑不用Cache 。
比如:可以创建一个Dictionary或者Hashtable的静态实例,它呢可好有骨干的缓存工作,
不了,我一旦提示您:您要和谐处理多线程访问数时之数并问题。
顺就说一样词:Hashtable.Synchronized(new
Hashtable())也是一个线程安全之集纳,如果想大概点,可以设想它。

通下去,我们来拘禁一下Cache的高级特性,这些还是Dictionary或者Hashtable不克好的。

归来顶部

缓存项的超时时

ASP.NET支持二种缓存项的晚点策略:绝对过期及滑动过期。 1.
断过期,这个好理解:就是于缓存放入Cache时,指定一个实际的年华。当岁月达指定的年华的经常,缓存项活动从Cache中移除。
2.
滑动过期:某些缓存项,我们恐怕仅望当产生用户以走访时,就硬着头皮保存在缓存中,只有当一段时间内用户不再访问该缓存项时,才转移除了其,
这样可以优化内存的使,因为这种方针可以保缓存的情节还是【很俏】的。
操作系统的内存和磁盘的复苏存不还是这样设计之呢?而立等同良实惠的特性,Cache也为咱准备好了,只要以以缓存项放入缓存时,
指定一个滑行过期时尽管可以兑现了。

上述二只选项分别针对应Add, Insert方法吃的DateTime absoluteExpiration,
TimeSpan slidingExpiration这第二个参数。
注意:这第二单参数还是成对采用的,但未能够而指定它们为一个【有效】值,最多只能一个参数值有效。
当不使其它一个参数码时,请用Cache类定义二独static readonly字段赋值。

立第二单参数比较简单,我就算非多说了,只说一样词:如果都以Noxxxxx这第二个选择,那么缓存项就直接保存于缓存中。(或许也会见给移除)

 

回顶部

缓存项的依赖性关系 – 依赖其它缓存项

ASP.NET
Cache有只要命强大的功效,那就算是缓存依赖。一个缓存项好借助让其它一个缓存项。
以下示例代码创建了次个缓存项,且她中有因关系。首先请看页面代码:
Ajax 6😉

<body>
    <p>Key1 的缓存内容:<%= HttpRuntime.Cache["key1"] %></p>
    <hr />

    <form action="CacheDependencyDemo.aspx" method="post">
        <input type="submit" name="SetKey1Cache" value="设置Key1的值" />
        <input type="submit" name="SetKey2Cache" value="设置Key2的值" />
    </form>
</body>

页面后台代码:Ajax 7😉

public partial class CacheDependencyDemo : System.Web.UI.Page
{
    [SubmitMethod(AutoRedirect=true)]
    private void SetKey1Cache()
    {
        SetKey2Cache();

        CacheDependency dep = new CacheDependency(null, new string[] { "key2" });
        HttpRuntime.Cache.Insert("key1", DateTime.Now.ToString(), dep, 
                                    Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration);
    }

    [SubmitMethod(AutoRedirect=true)]
    private void SetKey2Cache()
    {
        HttpRuntime.Cache.Insert("key2", Guid.NewGuid().ToString());
    }
}

当运行此示例页面时,运行结果如下图所著,
点击按钮【设置Key1的价值】时,将会晤出现缓存项的内容(左图)。点击按钮【设置Key2的值】时,此时拿取不至缓存项的始末(右图)。

Ajax 8

因结果并分析代码,我们可以看,在创立Key1的休息存项时,我们用了这种缓存依赖关系:

CacheDependency dep = new CacheDependency(null, new string[] { "key2" });

用,当我们创新Key2的苏存项时,Key1的休养存就失效了(不在)。

永不轻视了是示例。的确,仅看即几乎实行示范代码,或许它其实是没有什么意义。
那么,我虽举个实在的运状况来说明它们的用价值。

Ajax 9

上面立幅图是我形容的一个略带器。在示意图中,左下比赛是一个缓存表CacheTable,它由一个叫Table1BLL的类来保障。
CacheTable的数据来源Table1,由Table1.aspx页面显示出。 同时,ReportA,
ReportB的数量为重点根源Table1,由于Table1的拜访几乎绝大多数且是读多写少,所以,我将Table1的数额缓存起来了。
而且,ReportA,
ReportB这第二只表格采用GDI直接画有(由报表模块生成,可认是Table1BLL的上层类),鉴于这第二单表格的浏览次数比较多还数据源是朗诵多写少,
因此,这第二个表格的出口结果,我耶以它缓存起来。

每当是现象被,我们可想像一下:如果期待以Table1的数额发生修改后,如何让二只表格的休养存结果失效?
让Table1BLL去通知那次个表格模块,还是Table1BLL去一直删除二单表格的缓存?
其实,不管是选择前者还是后者,当以后还欲以Table1的CacheTable上做其他的休息存实现时(可能是另外的初报表),
那么,势必都设修改Table1BLL,那绝对是独败的规划。
这为毕竟模块间耦合的所带来的苦果。

正是,ASP.NET
Cache支持一种植叫做缓存依赖的特征,我们就待吃Table1BLL公开其缓存CacheTable的KEY就可了(假设KEY为
CacheTableKey),
然后,其它的休养存结果使一旦根据CacheTable,设置一下对准【CacheTableKey】的仗就好实现这样的效力:
当CacheTable更新后,被依的休养存结果以会见自动清除。然便根本地缓解了模块间的缓存数据依赖问题。

回顶部

缓存项的依赖性关系 – 文件指

于上篇博客【在.net中读写config文件之各种艺术】的最终,
我让大家留下了一个题材:
自盼望在用户改了配置文件后,程序会即时坐行的参数运行,而且并非还开网站。
今天本人不怕来对这个题目,并于有所要的一体贯彻代码。

率先,我要是验证一些:上次博客的题材,虽然缓解方案以及Cache的公文指有关,但还得和缓存的移除通知配合使用才会完善的化解问题。
为了方便内容的部署,我事先采用Cache的公文指来简单的实现一个粗的本子,在本文的存续有重新来完善是实现。

先行来拘禁个粗糙的版本。假如我之网站面临出诸如此类一个部署参数类型:
Ajax 10😉

/// <summary>
/// 模拟网站所需的运行参数
/// </summary>
public class RunOptions
{
    public string WebSiteUrl;
    public string UserName;
}

自可以将它们配置当如此一个XML文件被:

<?xml version="1.0" encoding="utf-8"?>
<RunOptions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
            xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <WebSiteUrl>http://www.cnblogs.com/fish-li</WebSiteUrl>
  <UserName>fish li</UserName>
</RunOptions>

再来一个用于展示运行参数的页面:
Ajax 11😉

<body>
    <p>WebSiteUrl: <%= WebSiteApp.RunOptions.WebSiteUrl %></p>
    <p>UserName: <%= WebSiteApp.RunOptions.UserName %></p>
</body>

下面的代码就可兑现:在XML修改后,浏览页面就能即刻看到最新的参数值
Ajax 12😉

public static class WebSiteApp
{
    private static readonly string RunOptionsCacheKey = Guid.NewGuid().ToString();

    public static RunOptions RunOptions
    {
        get
        {
            // 首先尝试从缓存中获取运行参数
            RunOptions options = HttpRuntime.Cache[RunOptionsCacheKey] as RunOptions;
            if( options == null ) {
                // 缓存中没有,则从文件中加载
                string path = HttpContext.Current.Server.MapPath("~/App_Data/RunOptions.xml");
                options = RwConfigDemo.XmlHelper.XmlDeserializeFromFile<RunOptions>(path, Encoding.UTF8);

                // 把从文件中读到的结果放入缓存,并设置与文件的依赖关系。
                CacheDependency dep = new CacheDependency(path);
                // 如果您的参数较复杂,与多个文件相关,那么也可以使用下面的方式,传递多个文件路径。
                //CacheDependency dep = new CacheDependency(new string[] { path });
                HttpRuntime.Cache.Insert(RunOptionsCacheKey, options, dep);
            }
            return options;
        }
    }
}

注意:这里还是是在应用CacheDependency,只是我们本是让她的构造函数的首先独参数传递要因之公文称。

每当快要寿终正寝对缓存的依介绍之前,还要加二点: 1.
CacheDependency尚支持【嵌套】,即:CacheDependency的构造函数中支持传入其它的CacheDependency实例,这样好组合一栽非常复杂的树状依赖关系。

  1. 缓存依赖的目标还足以是SQL SERVER,具体但参考SqlCacheDependency

回来顶部

缓存项的移除优先级

缓存的做法有好多种,一个静态变量也得以称作是一个缓存。一个静态的集纳就是一个缓存的容器了。
我思许多人数犹因此Dictionary,List,或者Hashtable做了缓存容器,我们得以用它来保存各种数码,改善程序的属性。
一般情况下,如果我们直接行使这仿佛集合去缓存各类数据,那么,那些数据所占有的内存以非会见叫回收,哪怕它的利用时连无是无数。
当缓存数据越来越多时,它们所消耗的内存自然为会见愈来愈多。那么,能免可知当内存不充沛时,释放掉一部分看不亟之复苏存项为?

以此题目为确确实实是个比较具体的题材。虽然,使用缓存会下程序运行更快,但是,我们数据会无限好,不容许全都缓存起来,
毕竟,内存空间是简单的。因此,我们得运用前所说的据悉一段时间内不再访问纵使去的国策来化解之题目。
然而,在咱们编码时,根本未知晓我们的顺序会运行在啊安排标准的电脑及,因此,根本未容许会见指向内存的轻重作出任何要,
此时,我们恐怕会见要当缓存占用了多之内存时,且当内存不够时,能自行移除一些请勿极端重要的苏存项,这可能为正如有意义。

对这需求,在.net
framework提供了亚栽解决办法,一种植是用WeakReference类,另一样种植是以Cache
。 不过,既然我们是于以ASP.NET,选择Cache当然会再也有利于。 在Cache的Add,
Insert方法的某些重载版本被,可以指定缓存项的保存优先级策略,由参数CacheItemPriority
priority来传。
其中,CacheItemPriority是一个枚举类型,它涵盖了之类枚举值:
Ajax 13😉

// 指定 Cache 对象中存储的项的相对优先级。
public enum CacheItemPriority
{
    //  在服务器释放系统内存时,具有该优先级级别的缓存项最有可能被从缓存删除。
    Low = 1,

    //  在服务器释放系统内存时,具有该优先级级别的缓存项比分配了 CacheItemPriority.Normal
    //  优先级的项更有可能被从缓存删除。
    BelowNormal = 2,

    //  在服务器释放系统内存时,具有该优先级级别的缓存项很有可能被从缓存删除,
    //  其被删除的可能性仅次于具有 CacheItemPriority.Low
    //  或 CacheItemPriority.BelowNormal 优先级的那些项。这是默认选项。
    Normal = 3,

    //  缓存项优先级的默认值为 CacheItemPriority.Normal。
    Default = 3,

    //  在服务器释放系统内存时,具有该优先级级别的缓存项被删除的可能性
    //  比分配了 CacheItemPriority.Normal 优先级的项要小。
    AboveNormal = 4,

    //  在服务器释放系统内存时,具有该优先级级别的缓存项最不可能被从缓存删除。
    High = 5,

    //  在服务器释放系统内存时,具有该优先级级别的缓存项将不会被自动从缓存删除。
    //  但是,具有该优先级级别的项会根据项的绝对到期时间或可调整到期时间与其他项一起被移除。
    NotRemovable = 6,
}

说明:当我们调用Cache的Add,
Insert方法时,如果非指定CacheItemPriority选项,最终采取Normal所表示的事先级。
如果我们意在用有可能无极端重要的数量放入缓存时,可以指定优先级吧Low或者BelowNormal。
如果想吃缓存项在内存不足时,也未会见为移除(除非到期或者靠项有改),可使NotRemovable。

明白,我们可采用是特性来支配缓存对内存压力的熏陶。
其它的缓存方案,如static Collection +
WeakReference也比较麻烦实现这样灵活的主宰。

返顶部

缓存项的移除通知

ASP.NET
Cache与局部static变量所实现的缓存效果并不相同,它的复苏存项是可依据局部特定的规范失效的,那些失效的休养存将会晤打内存中移除。
虽然,某些移除条件并无是出于咱们的代码直接解发的,但ASP.NET还是提供相同种方式被咱可以以缓存项在移除时,能通我们的代码。

顾啊:ASP.NET Cache支持移除【前】通知 和 移除【后】通知二种通知方式。

咱们得当调用Add,
Insert方法时,通过参数onRemoveCallback传递一个CacheItemRemovedCallback类型的托,以便在移除指定的休养生息存项时,
能够通知我们。这个委托的概念如下:
Ajax 14😉

/// <summary>
/// 定义在从 System.Web.Caching.Cache 移除缓存项时通知应用程序的回调方法。
/// </summary>
/// <param name="key">从缓存中移除的键(当初由Add, Insert传入的)。</param>
/// <param name="value">与从缓存中移除的键关联的缓存项(当初由Add, Insert传入的)。</param>
/// <param name="reason">从缓存移除项的原因。 </param>
public delegate void CacheItemRemovedCallback(string key, object value, CacheItemRemovedReason reason);


//  指定从 System.Web.Caching.Cache 对象移除项的原因。
public enum CacheItemRemovedReason
{
    //  该项是通过指定相同键的 Cache.Insert(System.String,System.Object)
    //  方法调用或 Cache.Remove(System.String) 方法调用从缓存中移除的。
    Removed = 1,

    //  从缓存移除该项的原因是它已过期。
    Expired = 2,

    //  之所以从缓存中移除该项,是因为系统要通过移除该项来释放内存。
    Underused = 3,

    //  从缓存移除该项的原因是与之关联的缓存依赖项已更改。
    DependencyChanged = 4,
}

委托的逐条参数的含义和移除原因,在诠释中还生明显的诠释,我呢不再另行了。
我眷恋:有多人知道Cache的Add,
Insert方法发生这参数,也晓得有此委托,但是,它们来啊用吗? 
在后的老二个小节中,我将提供次单示范来演示这等同精的职能。

一般性,我们见面坐下面这种艺术于Cache中获结果:

RunOptions options = HttpRuntime.Cache[RunOptionsCacheKey] as RunOptions;
if( options == null ) {
    // 缓存中没有,则从文件中加载
    // ..................................

    HttpRuntime.Cache.Insert(RunOptionsCacheKey, options, dep);
}
return options;

及时实际上为是一个惯用法了:先品尝从缓存中得到,如果没,则于数据源中加载,并再放入缓存。

胡会当顾Cache时返回null呢?答案就就是是二种因:1.
向来没放入Cache,2. 缓存项失效被移除了。
这种写法本身是从来不问题,可是,如果起数据源中加载数据的年月比较丰富,情况会如何为?
显然,会潜移默化后面第一破的看请求。您发出没产生思了,如果缓存项能一直位居Cache中,那不就可了嘛。
是的,通常来说,只要你于拿一个目标放入Cache时,不指定过期时,不点名缓存依赖,且设置为永不移除,那么对象真正会一直当Cache中,
可是,过期时间和缓存依赖也格外有因此啊。如何能二者兼顾得也?

为了化解此问题,微软在.net framework的3.5 SP1、3.0 SP1、2.0
SP1版本中,加入了【移除前通知】功能,不过,这个法只有吃Insert支持,
随之而来的还有一个委托以及一个移除原因之枚举定义:
Ajax 15😉

/// <summary>
/// 定义一个回调方法,用于在从缓存中移除缓存项之前通知应用程序。
/// </summary>
/// <param name="key">要从缓存中移除的项的标识符。</param>
/// <param name="reason">要从缓存中移除项的原因。</param>
/// <param name="expensiveObject">此方法返回时,包含含有更新的缓存项对象。</param>
/// <param name="dependency">此方法返回时,包含新的依赖项的对象。</param>
/// <param name="absoluteExpiration">此方法返回时,包含对象的到期时间。</param>
/// <param name="slidingExpiration">此方法返回时,包含对象的上次访问时间和对象的到期时间之间的时间间隔。</param>
public delegate void CacheItemUpdateCallback(string key, CacheItemUpdateReason reason, 
                out object expensiveObject, 
                out CacheDependency dependency, 
                out DateTime absoluteExpiration, 
                out TimeSpan slidingExpiration);

/// <summary>
/// 指定要从 Cache 对象中移除缓存项的原因。
/// </summary>
public enum CacheItemUpdateReason
{
    /// <summary>
    /// 指定要从缓存中移除项的原因是绝对到期或可调到期时间间隔已到期。
    /// </summary>
    Expired = 1,
    /// <summary>
    /// 指定要从缓存中移除项的原因是关联的 CacheDependency 对象发生了更改。
    /// </summary>
    DependencyChanged = 2,
}

只顾:CacheItemUpdateReason这个枚举只出次宗。原因要圈MSDN的解说:

跟 CacheItemRemovedReason 枚举不同,此枚举不含 Removed 或 Underused
值。可更新的复苏存项是不足移除的,因而不用会叫 ASP.NET
自动移除,即使需要释放内存为是如此。

再也同次等提醒:奇迹我们的确用缓存失效是特性,但是,缓存失效后会见被移除。
虽然我们得被持续之乞求于取不至缓存数据时,从数额源中加载,也得以以CacheItemRemovedCallback回调委托中,
重新加载缓存数据到Cache中,只是当多少的加载过程中,Cache并无含有我们所愿意的缓存数据,如果加载时间更加丰富,这种【空缺】效果啊会越加明白。
这样见面影响(后续的)其它要的访。为了保证给我们所幸的缓存数据能够直接存在于Cahce被,且以有失效机制,我们得以【移除前通知】功能。

回来顶部

赶巧用缓存项的移除通知 实现【延迟操作】

本人看了局部ASP.NET的书,也扣罢一些口形容的有关Cache方面的文章,基本上,要么是一带而过,要么就是举个毫无实际意义的以身作则。
可惜啊,这么强大的风味,我挺少见到有人拿它们之所以起来。

今日,我就算选个来实际意义的言传身教,再现Cache的兵不血刃作用!

自己发生诸如此类一个页面,可以为用户调整(上下运动)某个项目Ajax分记录的上线顺序:

Ajax 16

当用户需要调有修记下之岗位时,页面会弹有一个会话框,要求输入一个调由,并会发邮件通知所有相关人员。

Ajax 17

由于界面的限定,一不好操作(点击上产卵键头)只是拿同一长达记下移动一个岗位,当要针对性某修记下执行过多实践活动时,必须开展反复活动。
考虑到操作的方便性以及未为双重邮件的熏陶,程序用实现这样一个要求:
页面只要求输入一不成由纵然可以对相同条记下执行多次平移操作,并且不要反复发重复邮件,而且要求以最后的移动结果于邮件中发出来。

本条要求特别客观,毕竟谁都希望操作简捷。

那么哪些贯彻此需求为?这里而于第二个点来落实,首先,在页面上我们当要形成这效果,对同漫漫记下才弹一涂鸦对话框。
由于页面及劳动端的互全部采取Ajax方式开展(不刷新),状态好行使JS变量来维系,所以这个职能在页面中凡怪轻实现。
再来拘禁一下服务端,由于服务端并不曾另外状态,当然也得由页面把其的状态传给服务端,但是,哪次操作是最后一糟啊?
显然,这是无能为力理解之,最后只能修改需要,如果用户在2分钟之内不再操作有条记下时,便以近来相同不良操作视为最终一不行操作。

基于新的要求,程序必须记录用户之近期一律涂鸦操作,以便在2分钟无操作后,发出同样不成邮件,但只要含有第一次于输入的原因,
还答应涵盖最后之改动结果哦。

欠怎么落实此需求吗? 我当即就想到了ASP.NET
Cache,因为我打听其,知道其会帮助我完成这功效。下面我来说说在服务端是安贯彻之。

万事实现的思绪是: 1. 客户端页面还是每次用记录之RowGuid,
调整方向,调整原因,这三单参数发至服务端。 2.
服务端在处理终结顺序调整操作后,将要发送的邮件信息Insert到Cache中,同时提供slidingExpiration和onRemoveCallback参数。
3.
在CacheItemRemovedCallback回调委托中,忽小CacheItemRemovedReason.Removed的通,如果是其余的通报,则发邮件。

为好理解,我特别为大家准备了一个示范。整个示例由三组成部分组成:一个页面,一个JS文件,服务端代码。先来拘禁页面代码:
Ajax 18😉

<body>
    <p> 为了简单,示例页面只处理一条记录,且将记录的RowGuid直接显示出来。<br />
        实际场景中,这个RowGuid应该可以从一个表格的【当前选择行】中获取到。
    </p>
    <p> 当前选择行的 RowGuid = <%= Guid.NewGuid().ToString() %><br />
        当前选择行的 Sequence= 0
    </p>
    <p><input type="button" id="btnMoveUp" value="上移" />
        <input type="button" id="btnMoveDown" value="下移" />
    </p>
</body>

页面的显示力量如下:

Ajax 19

拍卖页面中第二个按钮的JS代码如下:
Ajax 20😉

// 用户输入的调整记录的原因
var g_reason = null;

$(function(){
    $("#btnMoveUp").click( function() { MoveRec(-1); } );
    $("#btnMoveDown").click( function() { MoveRec(1); } );
});

function MoveRec(direction){
    if( ~~($("#spanSequence").text()) + direction < 0 ){
        alert("已经不能上移了。");
        return;
    }
    if( g_reason == null ){
        g_reason = prompt("请输入调整记录顺序的原因:", "由于什么什么原因,我要调整...");
        if( g_reason == null )
            return;
    }

    $.ajax({
        url: "/AjaxDelaySendMail/MoveRec.fish",
        data: { RowGuid: $("#spanRowGuid").text(), 
                Direction: direction,
                Reason: g_reason
        },
        type: "POST", dataType: "text",
        success: function(responseText){
            $("#spanSequence").text(responseText);
        }
    });
}

证明:在服务端,我用了我于【用Asp.net写好之劳务框架】那么篇博客中提供的服务框架,
服务端的漫天代码是是样子的:(注意代码中之注释)
Ajax 21😉

/// <summary>
/// 移动记录的相关信息。
/// </summary>
public class MoveRecInfo
{
    public string RowGuid;
    public int Direction;
    public string Reason;
}


[MyService]
public class AjaxDelaySendMail
{
    [MyServiceMethod]
    public int MoveRec(MoveRecInfo info)
    {
        // 这里就不验证从客户端传入的参数了。实际开发中这个是必须的。

        // 先来调整记录的顺序,示例程序没有数据库,就用Cache来代替。
        int sequence = 0;
        int.TryParse(HttpRuntime.Cache[info.RowGuid] as string, out sequence);
        // 简单地示例一下调整顺序。
        sequence += info.Direction;
        HttpRuntime.Cache[info.RowGuid] = sequence.ToString();


        string key = info.RowGuid +"_DelaySendMail";
        // 这里我不直接发邮件,而是把这个信息放入Cache中,并设置2秒的滑过过期时间,并指定移除通知委托
        // 将操作信息放在缓存,并且以覆盖形式放入,这样便可以实现保存最后状态。
        // 注意:这里我用Insert方法。
        HttpRuntime.Cache.Insert(key, info, null, Cache.NoAbsoluteExpiration,
            TimeSpan.FromMinutes(2.0), CacheItemPriority.NotRemovable, MoveRecInfoRemovedCallback);

        return sequence;
    }    

    private void MoveRecInfoRemovedCallback(string key, object value, CacheItemRemovedReason reason)
    {
        if( reason == CacheItemRemovedReason.Removed )
            return;        // 忽略后续调用HttpRuntime.Cache.Insert()所触发的操作

        // 能运行到这里,就表示是肯定是缓存过期了。
        // 换句话说就是:用户2分钟再也没操作过了。

        // 从参数value取回操作信息
        MoveRecInfo info = (MoveRecInfo)value;
        // 这里可以对info做其它的处理。

        // 最后发一次邮件。整个延迟发邮件的过程就处理完了。
        MailSender.SendMail(info);
    }
}

为能够于JavaScript能一直调用C#遭受的方法,还索要在web.config中加入如下配置:

<httpHandlers>
    <add path="*.fish" verb="*" validate="false" type="MySimpleServiceFramework.AjaxServiceHandler"/>
</httpHandlers>

哼了,示例代码就是这些。如果你有趣味,可以以本文的结尾处下载这些示例代码,自己亲自感受一下利用Cache实现之【延迟处理】的效益。

其实这种【延迟处理】的机能是挺有因此底,比如还有一样种植适用场景:有些数据记录或得反复更新,如果老是换代都失去形容数据库,肯定会针对数据库造成一定之压力,
可是由这些数据也未是特别要,因此,我们可应用这种【延迟处理】来拿写数据库的机遇进行联合处理
最终我们好兑现:将反复底写照副变成一潮或少量的写入操作,我称这样效果啊:推合并写入

此间我虽对准数据库的缓合并写入供一个思路:将急需写入的数目记录放入Cache,调用Insert方法并提供slidingExpiration和onRemoveCallback参数,
然后在CacheItemRemovedCallback回调委托中,模仿我前面的以身作则代码,将反复改成一破。不过,这样可能会见起一个问题:如果数量是直接在修改,那么即便径直无会见刻画副数据库。
最后只要网站还开了,数据可能会见掉。如果担心这个题目,那么,可以于回调委托中,遇到CacheItemRemovedReason.Removed时,使用计数累加的点子,当到达一定数额后,
再写副数据库。比如:遇到10涂鸦CacheItemRemovedReason.Removed我就是形容一次等数据库,这样即便见面用原需要写10不成的数据库操作成一次于了。
当然矣,如果是另外移除原因,写数据库总是必不可少之。注意:于金额就类似敏感的数据,绝对不要采用这种艺术。

再次补偿二点: 1.
当CacheItemRemovedCallback回调委托给调用时,缓存项就休以Cache中了。 2.
每当CacheItemRemovedCallback回调委托中,我们还可将缓存项又放入缓存。
有没起想念过:这种规划得组合一个巡回?如果重成参数slidingExpiration便可实现一个定时器的效力。

有关缓存的失灵时,我而再次唤醒一点:通过absoluteExpiration,
slidingExpiration参数所传颂的时间,当缓存时间生效时,缓存对象并无会见就移除,
ASP.NET Cache大约为20秒的效率去反省这些早已不合时宜的苏存项。

回去顶部

赶巧用缓存项的移除通知 实现【自动加载配置文件】

以本文的先头有的【文件指】小节中,有一个演示演示了:当配置文件更新后,页面可以显得最新的改动结果。
在大示例中,为了简单,我一直将布参数放在Cache中,每次用时还从Cache中获取。
如果安排参数较多,这种做法或许为会见潜移默化性,毕竟配置参数并无见面经常修改,如果会直接看一个静态变量就能得到,应该会再也快。
通常,我们兴许会见如此做:
Ajax 22😉

private static RunOptions s_RunOptions;

public static RunOptions RunOptions
{
    // s_RunOptions 的初始化放在Init方法中了,会在Global.asax的Application_Start事件中调用。
    get { return s_RunOptions; }
}

public static RunOptions LoadRunOptions()
{
    string path = Path.Combine(AppDataPath, "RunOptions.xml");
    return RwConfigDemo.XmlHelper.XmlDeserializeFromFile<RunOptions>(path, Encoding.UTF8);
}

只是,这种做法来同等不够点便是:不可知以配置文件更新后,自动加载最新的布置结果。

为缓解此题材,我们得以下Cache提供的文本指与移除通知功能。
前面的演示演示了移除后通知功能,这里自己还演示一下移除前通知功能。
说明:事实上,完成这个力量,可以还采取移除后通报,只是移除前通知我还没以身作则,然而,这里用移除前通知并没有显得其的独有的力量。

下的代码演示了当配备文件修改后,自动更新运行参数的落实方式:(注意代码中之诠释)
Ajax 23😉

private static int s_RunOptionsCacheDependencyFlag = 0;

public static RunOptions LoadRunOptions()
{
    string path = Path.Combine(AppDataPath, "RunOptions.xml");
    // 注意啦:访问文件是可能会出现异常。不要学我,我写的是示例代码。
    RunOptions options = RwConfigDemo.XmlHelper.XmlDeserializeFromFile<RunOptions>(path, Encoding.UTF8);

    int flag = System.Threading.Interlocked.CompareExchange(ref s_RunOptionsCacheDependencyFlag, 1, 0);

    // 确保只调用一次就可以了。
    if( flag == 0 ) {
        // 让Cache帮我们盯住这个配置文件。
        CacheDependency dep = new CacheDependency(path);
        HttpRuntime.Cache.Insert(RunOptionsCacheKey, "Fish Li", dep,
            Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, RunOptionsUpdateCallback);
    }

    return options;
}

public static void RunOptionsUpdateCallback(
    string key, CacheItemUpdateReason reason, 
    out object expensiveObject, 
    out CacheDependency dependency, 
    out DateTime absoluteExpiration, 
    out TimeSpan slidingExpiration)
{
    // 注意哦:在这个方法中,不要出现【未处理异常】,否则缓存对象将被移除。

    // 说明:这里我并不关心参数reason,因为我根本就没有使用过期时间
    //        所以,只有一种原因:依赖的文件发生了改变。
    //        参数key我也不关心,因为这个方法是【专用】的。

    expensiveObject = "http://www.cnblogs.com/fish-li/";
    dependency = new CacheDependency(Path.Combine(AppDataPath, "RunOptions.xml"));
    absoluteExpiration = Cache.NoAbsoluteExpiration;
    slidingExpiration = Cache.NoSlidingExpiration;

    // 重新加载配置参数
    s_RunOptions = LoadRunOptions();
}

反很有点,只是LoadRunOptions方法做了改动了罢了,但是意义倒是生老。

还记得自己当上篇博客【在.net中读写config文件的各种办法】的结尾处留下来的题材为?
这个示例就是本身之缓解方案。

返顶部

文件监视技术的抉择

于文本监视,我怀念有人也许会想到FileSystemWatcher。正好我不怕来说说关于【文件监视技术】的选项题材。
说明,本文所有结论均为己个人的见,仅供参考。

以此组件,早于做WinForm开发时虽因故了了,对它吗是记忆比较深的。
它来一个卷入不好的地方是:事件会重新发生。以:一赖文件的保存操作,它可引发了次潮事件。
什么,你免信教? 正好,我还准备了一个演示程序。

Ajax 24

说明:图片被展示了起了二蹩脚事件,但本身只是当改了文件后,做了同一次等保存操作而已。
本文的最后处发生我之以身作则程序,您可好失去摸索一下。这里为便于,还是贴出相关代码:
Ajax 25😉

private void Form1_Shown(object sender, EventArgs e)
{
    this.fileSystemWatcher1.Path = Environment.CurrentDirectory;
    this.fileSystemWatcher1.Filter = "RunOptions.xml";
    this.fileSystemWatcher1.NotifyFilter = System.IO.NotifyFilters.LastWrite;
    this.fileSystemWatcher1.EnableRaisingEvents = true;            
}

private void fileSystemWatcher1_Changed(object sender, System.IO.FileSystemEventArgs e)
{
    string message = string.Format("{0} {1}.", e.Name, e.ChangeType);
    this.listBox1.Items.Add(message);
}

对这个仿佛的采用,只想说一些:会掀起的波很多,因此一定要顾过滤。以下引述MSDN的一致截说明:

Windows 操作系统在 FileSystemWatcher
创建的缓冲区中通报组件文件来变更。如果缺少日内发出多改动,则缓冲区可能会见漫起。这将造成组件失去对目录更改的跟,并且它们将单纯提供日常通知。使用
InternalBufferSize
属性来增加缓冲区大小的支出比较生,因为其出自无法换来至磁盘的非页面内存,所以应确保缓冲区大小适宜(尽量小,但为如有足大小以便不见面少任何文件更改事件)。若使避缓冲区溢起,请以
NotifyFilter 和 IncludeSubdirectories
属性,以便可以筛选掉不思要之转移通知。

侥幸的凡,ASP.NET
Cache并从未动用这组件,我们不要操心文件指而吸引的双重操作问题。
它一直依赖让webengine.dll所提供的API,因此,建议于ASP.NET应用程序中,优先采取Cache所提供的文本指功能。

归来顶部

各种缓存方案的并存

ASP.NET
Cache是同种缓存技术,然而,我们当ASP.NET程序中尚好采用外的休息存技术,
这些不同之缓存也每发生个别的优点。由于ASP.NET
Cache不能够提供对外访问能力,因此,它不可能代表以memcached为表示的分布式缓存技术,
但它由是无欲跨越进程看,效率为比分布式缓存的速还快。如果以ASP.NET
Cache设计成为【一级缓存】,
分布式缓存设计成【二级缓存】,就像CPU的缓存那样,那么以能够以利用二者的保有的亮点,实现还健全的效应跟速度。

其实缓存是从未一个显然定义的技能,一个static变量也是一个缓存,一个static集合就是一个缓存容器了。
这种缓存与ASP.NET
Cache相比起来,显然static变量的访问速度会重复快,如果static集合不是设计得够呛不同之言语,
并发的冲突为恐怕会见比ASP.NET
Cache小,也正是以马上或多或少,static集合也存有广泛的用。 然而,ASP.NET
Cache的有的高级功能,如:过期时,缓存依赖(包含文件指),移除通知,也是static集合不富有的。
因此,合理地而采取她,会叫程序有所无限好的性质,也以具有双重强硬的职能。

 

点击这里下载示例代码

 

相关文章