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

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

读书目录

众多做进度序品质优化的人,或者关怀进程程序品质的人,应该都应用过各个缓存技术。
而我前几天所说的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方法中的Date提姆e absoluteExpiration,
提姆eSpan 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或者Below诺玛l。
假若想让缓存项在内存不足时,也不会被移除(除非到期或者重视项有变动),可应用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 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文件的各个措施】的结尾处留下来的难题呢?
那几个示例就是自个儿的解决方案。

回到顶部

文本监视技术的挑选

对于文本监视,我想有人或许会想到FileSystem沃特cher。正好我就来说说关于【文件监视技术】的挑三拣四难题。
表明,本文所有结论均为本人个人的理念,仅供参考。

这一个组件,早在做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集合不负有的。
因而,合理地同时利用它们,会让程序有所最好的特性,也同时所有更有力的法力。

 

点击那里下载示例代码

 

相关文章