ASP.NET 缓存技术分析

cache 的基本概念 cache 是学习 ACE 协议的基础,是 CPU 结构中重要的组成部分。 阅读详情

转自:http://blog.youkuaiyun.com/sundacheng1989/article/details/10001315

缓存功能是大型网站设计一个很重要的部分。由数据库驱动的Web应用程序,如果需要改善其性能,最好的方法是使用缓存功能。可能的情况下尽量使用缓存,从内存中返回数据的速度始终比去数据库查的速度快,因而可以大大提供应用程序的性能。毕竟现在内存非常便宜,用空间换取时间效率应该是非常划算的。尤其是对耗时比较长的、需要建立网络链接的数据库查询操作等。 对于web页面的缓存,WebForm与ASP.NET MVC有不同的语法。在WebForm中, <%@ OutputCache Duration="60" VaryByParam="none" %>  来进行页面缓存或者局部页面缓存,在ASP.NET MVC中,针对Action使用Attribute进行页面缓存。这些都是Web框架封装好的,而对于Web Application来讲,需要缓存的不仅仅是页面,很多时候还需要定制的去缓存一些内容,这时候就需要用到HttpRun.Cache对象。

以下内容转自   细说 ASP.NET Cache 及其高级用法

许多做过程序性能优化的人,或者关注过程程序性能的人,应该都使用过各类缓存技术。 而我今天所说的Cache是专指ASP.NET的Cache,我们可以使用HttpRuntime.Cache访问到的那个Cache,而不是其它的缓存技术。

Cache的基本用途

提到Cache,不得不说说它的主要功能:改善程序性能。
ASP.NET是一种动态页面技术,用ASP.NET技术做出来的网页几乎都是动态的,所谓动态是指:页面的内容会随着不同的用户或者持续更新的数据, 而呈现出不同的显示结果。既然是动态的,那么这些动态的内容是从哪里来的呢?我想绝大多数网站都有自己的数据源, 程序通过访问数据源获取页面所需的数据,然后根据一些业务规则的计算处理,最后变成适合页面展示的内容。

由于这种动态页面技术通常需要从数据源获取数据,并经过一些计算逻辑,最终变成一些HTML代码发给客户端显示。而这些计算过程显然也是有成本的。 这些处理成本最直接可表现为影响服务器的响应速度,尤其是当数据的处理过程变得复杂以及访问量变大时,会变得比较明显。 另一方面,有些数据并非时刻在发生变化,如果我们可以将一些变化不频繁的数据的最终计算结果(包括页面输出)缓存起来, 就可以非常明显地提升程序的性能,缓存的最常见且最重要的用途就体现在这个方面。 这也是为什么一说到性能优化时,一般都将缓存摆在第一位的原因。 我今天要说到的ASP.NET Cache也是可以实现这种缓存的一种技术。 不过,它还有其它的一些功能,有些是其它缓存技术所没有的。

Cache的定义

在介绍Cache的用法前,我们先来看一下Cache的定义:(说明:我忽略了一些意义不大的成员)

<span style="color: green;">// 实现用于 Web 应用程序的缓存。无法继承此类。
</span><span style="color: blue;">public sealed class </span><span style="color: rgb(43, 145, 175);">Cache </span>: <span style="color: rgb(43, 145, 175);">IEnumerable
</span>{
    <span style="color: green;">// 用于 Cache.Insert(...) 方法调用中的 absoluteExpiration 参数中以指示项从不过期。
    </span><span style="color: blue;">public static readonly </span><span style="color: rgb(43, 145, 175);">DateTime </span>NoAbsoluteExpiration;

    <span style="color: green;">// 用作 Cache.Insert(...) 或 Cache.Add(...)
    //       方法调用中的 slidingExpiration 参数,以禁用可调过期。
    </span><span style="color: blue;">public static readonly </span><span style="color: rgb(43, 145, 175);">TimeSpan </span>NoSlidingExpiration;


    <span style="color: green;">// 获取或设置指定键处的缓存项。
    </span><span style="color: blue;">public object this</span>[<span style="color: blue;">string </span>key] { <span style="color: blue;">get</span>; <span style="color: blue;">set</span>; }


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


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


    <span style="color: blue;">public void </span>Insert(<span style="color: blue;">string </span>key, <span style="color: blue;">object </span>value);
    <span style="color: blue;">public void </span>Insert(<span style="color: blue;">string </span>key, <span style="color: blue;">object </span>value, <span style="color: rgb(43, 145, 175);">CacheDependency </span>dependencies);
    <span style="color: blue;">public void </span>Insert(<span style="color: blue;">string </span>key, <span style="color: blue;">object </span>value, <span style="color: rgb(43, 145, 175);">CacheDependency </span>dependencies,
                                    <span style="color: rgb(43, 145, 175);">DateTime </span>absoluteExpiration, <span style="color: rgb(43, 145, 175);">TimeSpan </span>slidingExpiration);

    <span style="color: green;">// 摘要:
    //     向 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 或大于一年的等效值。
    </span><span style="color: blue;">public void </span>Insert(<span style="color: blue;">string </span>key, <span style="color: blue;">object </span>value, <span style="color: rgb(43, 145, 175);">CacheDependency </span>dependencies,
                        <span style="color: rgb(43, 145, 175);">DateTime </span>absoluteExpiration, <span style="color: rgb(43, 145, 175);">TimeSpan </span>slidingExpiration,
                        <span style="color: rgb(43, 145, 175);">CacheItemPriority </span>priority, <span style="color: rgb(43, 145, 175);">CacheItemRemovedCallback </span>onRemoveCallback);

    <span style="color: green;">// 从应用程序的 System.Web.Caching.Cache 对象移除指定项。
    </span><span style="color: blue;">public object </span>Remove(<span style="color: blue;">string </span>key);

    <span style="color: green;">// 将对象与依赖项策略、到期策略和优先级策略
    // 以及可用来在从缓存中移除项【之前】通知应用程序的委托一起插入到 Cache 对象中。
    // 注意:此方法受以下版本支持:3.5 SP1、3.0 SP1、2.0 SP1
    </span><span style="color: blue;">public void </span>Insert(<span style="color: blue;">string </span>key, <span style="color: blue;">object </span>value, <span style="color: rgb(43, 145, 175);">CacheDependency </span>dependencies,
                            <span style="color: rgb(43, 145, 175);">DateTime </span>absoluteExpiration, <span style="color: rgb(43, 145, 175);">TimeSpan </span>slidingExpiration,
                            <span style="color: rgb(43, 145, 175);">CacheItemUpdateCallback </span>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方法都有许多参数,有时我们或许只是想简单地放入缓存,一切接受默认值,那么还可以调用它的默认索引器, 我们来看一下这个索引器是如何工作的:

<span style="color: blue;">public object this</span>[<span style="color: blue;">string </span>key]
{
    <span style="color: blue;">get
    </span>{
        <span style="color: blue;">return this</span><span style="color: red;">.</span>Get(key);
    }
    <span style="color: blue;">set
    </span>{
        <span style="color: blue;">this</span><span style="color: red;">.</span>Insert(key, <span style="color: blue;">value</span>);
    }
}

可以看到:读缓存,其实是在调用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创建的核心过程:

<span style="color: blue;">internal static </span><span style="color: rgb(43, 145, 175);">CacheInternal </span>Create()
{
    <span style="color: rgb(43, 145, 175);">CacheInternal </span>internal2;
    <span style="color: blue;">int </span>numSingleCaches <span style="color: red;">= </span><span style="color: purple;">0</span>;
    <span style="color: blue;">if</span>( numSingleCaches <span style="color: red;">== </span><span style="color: purple;">0 </span>) {
        <span style="color: blue;">uint </span>numProcessCPUs <span style="color: red;">= </span>(<span style="color: blue;">uint</span>)<span style="color: rgb(43, 145, 175);">SystemInfo</span><span style="color: red;">.</span>GetNumProcessCPUs();
        numSingleCaches <span style="color: red;">= </span><span style="color: purple;">1</span>;
        <span style="color: blue;">for</span>( numProcessCPUs <span style="color: red;">-= </span><span style="color: purple;">1</span>; numProcessCPUs <span style="color: red;">> </span><span style="color: purple;">0</span>; numProcessCPUs <span style="color: red;">= </span>numProcessCPUs <span style="color: red;">>> </span><span style="color: purple;">1 </span>) {
            numSingleCaches <span style="color: red;">= </span>numSingleCaches <span style="color: red;"><< </span><span style="color: purple;">1</span>;
        }
    }
    <span style="color: rgb(43, 145, 175);">CacheCommon </span>cacheCommon <span style="color: red;">= </span><span style="color: blue;">new </span><span style="color: rgb(43, 145, 175);">CacheCommon</span>();
    <span style="color: blue;">if</span>( numSingleCaches <span style="color: red;">== </span><span style="color: purple;">1 </span>) {
        internal2 <span style="color: red;">= </span><span style="color: blue;">new </span><span style="color: rgb(43, 145, 175);">CacheSingle</span>(cacheCommon, <span style="color: blue;">null</span>, <span style="color: purple;">0</span>);
    }
    <span style="color: blue;">else </span>{
        internal2 <span style="color: red;">= </span><span style="color: blue;">new </span><span style="color: rgb(43, 145, 175);">CacheMultiple</span>(cacheCommon, numSingleCaches);
    }
    cacheCommon<span style="color: red;">.</span>SetCacheInternal(internal2);
    cacheCommon<span style="color: red;">.</span>ResetFromConfigSettings();
    <span style="color: blue;">return </span>internal2;
}

说明:CacheInternal是个内部用的包装类,Cache的许多操作都要由它来完成。

在上面的代码中,numSingleCaches的计算过程很重要,如果上面代码不容易理解,那么请看我下面的示例代码: 

<span style="color: blue;">static void </span>Main()
{
    <span style="color: blue;">for</span>( <span style="color: blue;">uint </span>i <span style="color: red;">= </span><span style="color: purple;">1</span>; i <span style="color: red;"><= </span><span style="color: purple;">20</span>; i<span style="color: red;">++ </span>)
        ShowCount(i);            
}
<span style="color: blue;">static void </span>ShowCount(<span style="color: blue;">uint </span>numProcessCPUs)
{
    <span style="color: blue;">int </span>numSingleCaches <span style="color: red;">= </span><span style="color: purple;">1</span>;
    <span style="color: blue;">for</span>( numProcessCPUs <span style="color: red;">-= </span><span style="color: purple;">1</span>; numProcessCPUs <span style="color: red;">> </span><span style="color: purple;">0</span>; numProcessCPUs <span style="color: red;">= </span>numProcessCPUs <span style="color: red;">>> </span><span style="color: purple;">1 </span>) {
        numSingleCaches <span style="color: red;">= </span>numSingleCaches <span style="color: red;"><< </span><span style="color: purple;">1</span>;
    }
    <span style="color: rgb(43, 145, 175);">Console</span><span style="color: red;">.</span>Write(numSingleCaches <span style="color: red;">+ </span><span style="color: rgb(163, 21, 21);">","</span>);
}

程序将会输出:

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

CacheMultiple的构造函数如下:

<span style="color: blue;">internal </span>CacheMultiple(<span style="color: rgb(43, 145, 175);">CacheCommon </span>cacheCommon, <span style="color: blue;">int </span>numSingleCaches) : <span style="color: blue;">base</span>(cacheCommon)
{
    <span style="color: blue;">this</span><span style="color: red;">.</span>_cacheIndexMask <span style="color: red;">= </span>numSingleCaches <span style="color: red;">- </span><span style="color: purple;">1</span>;
    <span style="color: blue;">this</span><span style="color: red;">.</span>_caches <span style="color: red;">= </span><span style="color: blue;">new </span><span style="color: rgb(43, 145, 175);">CacheSingle</span>[numSingleCaches];
    <span style="color: blue;">for </span>(<span style="color: blue;">int </span>i <span style="color: red;">= </span><span style="color: purple;">0</span>; i <span style="color: red;">< </span>numSingleCaches; i<span style="color: red;">++</span>)
    {
        <span style="color: blue;">this</span><span style="color: red;">.</span>_caches[i] <span style="color: red;">= </span><span style="color: blue;">new </span><span style="color: rgb(43, 145, 175);">CacheSingle</span>(cacheCommon, <span style="color: blue;">this</span>, i);
    }
}

现在您应该明白了吧:CacheSingle其实是ASP.NET内部使用的缓存容器,多个CPU时,它会创建多个缓存容器。
在写入时,它是如何定位这些容器的呢?请继续看代码:

<span style="color: blue;">internal </span><span style="color: rgb(43, 145, 175);">CacheSingle </span>GetCacheSingle(<span style="color: blue;">int </span>hashCode)
{
    hashCode <span style="color: red;">= </span><span style="color: rgb(43, 145, 175);">Math</span><span style="color: red;">.</span>Abs(hashCode);
    <span style="color: blue;">int </span>index <span style="color: red;">= </span>hashCode <span style="color: red;">& </span><span style="color: blue;">this</span><span style="color: red;">.</span>_cacheIndexMask;
    <span style="color: blue;">return this</span><span style="color: red;">.</span>_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有个很强大的功能,那就是缓存依赖。一个缓存项可以依赖于另一个缓存项。 以下示例代码创建了二个缓存项,且它们间有依赖关系。首先请看页面代码:

<span style="color: blue;"><</span><span style="color: rgb(163, 21, 21);">body</span><span style="color: blue;">>
    <</span><span style="color: rgb(163, 21, 21);">p</span><span style="color: blue;">></span>Key1 的缓存内容:<span style="background-color: rgb(255, 238, 98);"><%</span><span style="color: blue;">= </span><span style="color: rgb(43, 145, 175);">HttpRuntime</span><span style="color: red;">.</span>Cache[<span style="color: rgb(163, 21, 21);">"key1"</span>] <span style="background-color: rgb(255, 238, 98);">%></span><span style="color: blue;"></</span><span style="color: rgb(163, 21, 21);">p</span><span style="color: blue;">>
    <</span><span style="color: rgb(163, 21, 21);">hr </span><span style="color: blue;">/>
        
    <</span><span style="color: rgb(163, 21, 21);">form </span><span style="color: red;">action</span><span style="color: blue;">="CacheDependencyDemo.aspx" </span><span style="color: red;">method</span><span style="color: blue;">="post">
        <</span><span style="color: rgb(163, 21, 21);">input </span><span style="color: red;">type</span><span style="color: blue;">="submit" </span><span style="color: red;">name</span><span style="color: blue;">="SetKey1Cache" </span><span style="color: red;">value</span><span style="color: blue;">="设置Key1的值" />
        <</span><span style="color: rgb(163, 21, 21);">input </span><span style="color: red;">type</span><span style="color: blue;">="submit" </span><span style="color: red;">name</span><span style="color: blue;">="SetKey2Cache" </span><span style="color: red;">value</span><span style="color: blue;">="设置Key2的值" />
    </</span><span style="color: rgb(163, 21, 21);">form</span><span style="color: blue;">>
</</span><span style="color: rgb(163, 21, 21);">body</span><span style="color: blue;">></span>

页面后台代码:

<span style="color: blue;">public partial class </span><span style="color: rgb(43, 145, 175);">CacheDependencyDemo </span>: System<span style="color: red;">.</span>Web<span style="color: red;">.</span>UI<span style="color: red;">.</span><span style="color: rgb(43, 145, 175);">Page
</span>{
    [<span style="color: rgb(43, 145, 175);">SubmitMethod</span>(AutoRedirect<span style="color: red;">=</span><span style="color: blue;">true</span>)]
    <span style="color: blue;">private void </span>SetKey1Cache()
    {
        SetKey2Cache();

        <span style="color: rgb(43, 145, 175);">CacheDependency </span>dep <span style="color: red;">= </span><span style="color: blue;">new </span><span style="color: rgb(43, 145, 175);">CacheDependency</span>(<span style="color: blue;">null</span>, <span style="color: blue;">new string</span>[] { <span style="color: rgb(163, 21, 21);">"key2" </span>});
        <span style="color: rgb(43, 145, 175);">HttpRuntime</span><span style="color: red;">.</span>Cache<span style="color: red;">.</span>Insert(<span style="color: rgb(163, 21, 21);">"key1"</span>, <span style="color: rgb(43, 145, 175);">DateTime</span><span style="color: red;">.</span>Now<span style="color: red;">.</span>ToString(), dep, 
                                    <span style="color: rgb(43, 145, 175);">Cache</span><span style="color: red;">.</span>NoAbsoluteExpiration, <span style="color: rgb(43, 145, 175);">Cache</span><span style="color: red;">.</span>NoSlidingExpiration);
    }

    [<span style="color: rgb(43, 145, 175);">SubmitMethod</span>(AutoRedirect<span style="color: red;">=</span><span style="color: blue;">true</span>)]
    <span style="color: blue;">private void </span>SetKey2Cache()
    {
        <span style="color: rgb(43, 145, 175);">HttpRuntime</span><span style="color: red;">.</span>Cache<span style="color: red;">.</span>Insert(<span style="color: rgb(163, 21, 21);">"key2"</span>, <span style="color: rgb(43, 145, 175);">Guid</span><span style="color: red;">.</span>NewGuid()<span style="color: red;">.</span>ToString());
    }
}

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

根据结果并分析代码,我们可以看出,在创建Key1的缓存项时,我们使用了这种缓存依赖关系:

<span style="color: rgb(43, 145, 175);">CacheDependency </span>dep <span style="color: red;">= </span><span style="color: blue;">new </span><span style="color: rgb(43, 145, 175);">CacheDependency</span>(<span style="color: blue;">null</span>, <span style="color: blue;">new string</span>[] { <span style="color: rgb(163, 21, 21);">"key2" </span>});

所以,当我们更新Key2的缓存项时,Key1的缓存就失效了(不存在)。

不要小看了这个示例。的确,仅看这几行示例代码,或许它们实在是没有什么意义。 那么,我就举个实际的使用场景来说明它的使用价值。

上面这幅图是我写的一个小工具。在示意图中,左下角是一个缓存表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更新后,被依赖的缓存结果将会自动清除。这样就彻底地解决了模块间的缓存数据依赖问题。

缓存项的依赖关系 - 文件依赖

我希望在用户修改了配置文件后,程序能立刻以最新的参数运行,而且不用重启网站。
首先,我要说明一点,虽然解决方案与Cache的文件依赖有关,但还需与缓存的移除通知配合使用才能完美的解决问题。 为了便于内容的安排,我先使用Cache的文件依赖来简单的实现一个粗糙的版本,在本文的后续部分再来完善这个实现。

先来看个粗糙的版本。假如我的网站中有这样一个配置参数类型:

<span style="color: gray;">/// <summary>
/// </span><span style="color: green;">模拟网站所需的运行参数
</span><span style="color: gray;">/// </summary>
</span><span style="color: blue;">public class </span><span style="color: rgb(43, 145, 175);">RunOptions
</span>{
    <span style="color: blue;">public string </span>WebSiteUrl;
    <span style="color: blue;">public string </span>UserName;
}

我可以将它配置在这样一个XML文件中:

<span style="color: blue;"><?</span><span style="color: rgb(163, 21, 21);">xml </span><span style="color: red;">version</span><span style="color: blue;">=</span>"<span style="color: blue;">1.0</span>" <span style="color: red;">encoding</span><span style="color: blue;">=</span>"<span style="color: blue;">utf-8</span>"<span style="color: blue;">?>
<</span><span style="color: rgb(163, 21, 21);">RunOptions </span><span style="color: red;">xmlns:xsi</span><span style="color: blue;">=</span>"<span style="color: blue;">http://www.w3.org/2001/XMLSchema-instance</span>" 
            <span style="color: red;">xmlns:xsd</span><span style="color: blue;">=</span>"<span style="color: blue;">http://www.w3.org/2001/XMLSchema</span>"<span style="color: blue;">>
  <</span><span style="color: rgb(163, 21, 21);">WebSiteUrl</span><span style="color: blue;">></span>http://www.cnblogs.com/fish-li<span style="color: blue;"></</span><span style="color: rgb(163, 21, 21);">WebSiteUrl</span><span style="color: blue;">>
  <</span><span style="color: rgb(163, 21, 21);">UserName</span><span style="color: blue;">></span>fish li<span style="color: blue;"></</span><span style="color: rgb(163, 21, 21);">UserName</span><span style="color: blue;">>
</</span><span style="color: rgb(163, 21, 21);">RunOptions</span><span style="color: blue;">></span>

再来一个用于显示运行参数的页面: 

<span style="color: blue;"><</span><span style="color: rgb(163, 21, 21);">body</span><span style="color: blue;">>
    <</span><span style="color: rgb(163, 21, 21);">p</span><span style="color: blue;">></span>WebSiteUrl: <span style="background-color: rgb(255, 238, 98);"><%</span><span style="color: blue;">= </span><span style="color: rgb(43, 145, 175);">WebSiteApp</span><span style="color: red;">.</span>RunOptions<span style="color: red;">.</span>WebSiteUrl <span style="background-color: rgb(255, 238, 98);">%></span><span style="color: blue;"></</span><span style="color: rgb(163, 21, 21);">p</span><span style="color: blue;">>
    <</span><span style="color: rgb(163, 21, 21);">p</span><span style="color: blue;">></span>UserName: <span style="background-color: rgb(255, 238, 98);"><%</span><span style="color: blue;">= </span><span style="color: rgb(43, 145, 175);">WebSiteApp</span><span style="color: red;">.</span>RunOptions<span style="color: red;">.</span>UserName <span style="background-color: rgb(255, 238, 98);">%></span><span style="color: blue;"></</span><span style="color: rgb(163, 21, 21);">p</span><span style="color: blue;">>
</</span><span style="color: rgb(163, 21, 21);">body</span><span style="color: blue;">></span>

下面的代码就可以实现:在XML修改后,浏览页面就能立即看到最新的参数值:

<span style="color: blue;">public static class </span><span style="color: rgb(43, 145, 175);">WebSiteApp
</span>{
    <span style="color: blue;">private static readonly string </span>RunOptionsCacheKey <span style="color: red;">= </span><span style="color: rgb(43, 145, 175);">Guid</span><span style="color: red;">.</span>NewGuid()<span style="color: red;">.</span>ToString();

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

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

注意:这里仍然是在使用CacheDependency,只是我们现在是给它的构造函数的第一个参数传递要依赖的文件名。

在即将结束对缓存的依赖介绍之前,还要补充二点:
1. CacheDependency还支持【嵌套】,即:CacheDependency的构造函数中支持传入其它的CacheDependency实例,这样可以构成一种非常复杂的树状依赖关系。
2. 缓存依赖的对象还可以是SQL SERVER,具体可参考SqlCacheDependency

缓存项的移除优先级

缓存的做法有很多种,一个静态变量也可以称为是一个缓存。一个静态的集合就是一个缓存的容器了。 我想很多人都用Dictionary,List,或者Hashtable做过缓存容器,我们可以使用它们来保存各种数据,改善程序的性能。 一般情况下,如果我们直接使用这类集合去缓存各类数据,那么,那些数据所占用的内存将不会被回收,哪怕它们的使用机会并不是很多。 当缓存数据越来越多时,它们所消耗的内存自然也会越来越多。那么,能不能在内存不充足时,释放掉一些访问不频繁的缓存项呢?

这个问题也确实是个较现实的问题。虽然,使用缓存会使用程序运行更快,但是,我们数据会无限大,不可能统统缓存起来, 毕竟,内存空间是有限的。因此,我们可以使用前面所说的基于一段时间内不再访问就删除的策略来解决这个问题。 然而,在我们编码时,根本不知道我们的程序会运行在什么配置标准的计算机上,因此,根本不可能会对内存的大小作出任何假设, 此时,我们可能会希望当缓存占用过多的内存时,且当内存不够时,能自动移除一些不太重要的缓存项,这或许也比较有意义。

对于这个需求,在.net framework提供了二种解决办法,一种是使用WeakReference类,另一种是使用Cache 。 不过,既然我们是在使用ASP.NET,选择Cache当然会更方便。 在Cache的Add, Insert方法的某些重载版本中,可以指定缓存项的保存优先级策略,由参数CacheItemPriority priority来传入。 其中,CacheItemPriority是一个枚举类型,它包含了如下枚举值:

<span style="color: green;">// 指定 Cache 对象中存储的项的相对优先级。
</span><span style="color: blue;">public enum </span><span style="color: rgb(43, 145, 175);">CacheItemPriority
</span>{
    <span style="color: green;">//  在服务器释放系统内存时,具有该优先级级别的缓存项最有可能被从缓存删除。
    </span>Low <span style="color: red;">= </span><span style="color: purple;">1</span>,

    <span style="color: green;">//  在服务器释放系统内存时,具有该优先级级别的缓存项比分配了 CacheItemPriority.Normal
    //  优先级的项更有可能被从缓存删除。
    </span>BelowNormal <span style="color: red;">= </span><span style="color: purple;">2</span>,

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

    <span style="color: green;">//  缓存项优先级的默认值为 CacheItemPriority.Normal。
    </span>Default <span style="color: red;">= </span><span style="color: purple;">3</span>,

    <span style="color: green;">//  在服务器释放系统内存时,具有该优先级级别的缓存项被删除的可能性
    //  比分配了 CacheItemPriority.Normal 优先级的项要小。
    </span>AboveNormal <span style="color: red;">= </span><span style="color: purple;">4</span>,

    <span style="color: green;">//  在服务器释放系统内存时,具有该优先级级别的缓存项最不可能被从缓存删除。
    </span>High <span style="color: red;">= </span><span style="color: purple;">5</span>,

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

说明:当我们调用Cache的Add, Insert方法时,如果不指定CacheItemPriority选项,最终使用Normal所代表的优先级。 如果我们希望将某个可能不太重要的数据放入缓存时,可以指定优先级为Low或者BelowNormal。 如果想让缓存项在内存不足时,也不会被移除(除非到期或者依赖项有改变),可使用NotRemovable。

显然,我们可以使用这个特性来控制缓存对内存压力的影响。 其它的缓存方案,如static Collection + WeakReference也较难实现这样灵活的控制。

缓存项的移除通知

ASP.NET Cache与一些static变量所实现的缓存效果并不相同,它的缓存项是可以根据一些特定的条件失效的,那些失效的缓存将会从内存中移除。 虽然,某些移除条件并不是由我们的代码直接解发的,但ASP.NET还是提供一种方法让我们可以在缓存项在移除时,能通知我们的代码。

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

我们可以在调用Add, Insert方法时,通过参数onRemoveCallback传递一个CacheItemRemovedCallback类型的委托,以便在移除指定的缓存项时, 能够通知我们。这个委托的定义如下:

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


<span style="color: green;">//  指定从 System.Web.Caching.Cache 对象移除项的原因。
</span><span style="color: blue;">public enum </span><span style="color: rgb(43, 145, 175);">CacheItemRemovedReason
</span>{
    <span style="color: green;">//  该项是通过指定相同键的 Cache.Insert(System.String,System.Object)
    //  方法调用或 Cache.Remove(System.String) 方法调用从缓存中移除的。
    </span>Removed <span style="color: red;">= </span><span style="color: purple;">1</span>,

    <span style="color: green;">//  从缓存移除该项的原因是它已过期。
    </span>Expired <span style="color: red;">= </span><span style="color: purple;">2</span>,

    <span style="color: green;">//  之所以从缓存中移除该项,是因为系统要通过移除该项来释放内存。
    </span>Underused <span style="color: red;">= </span><span style="color: purple;">3</span>,

    <span style="color: green;">//  从缓存移除该项的原因是与之关联的缓存依赖项已更改。
    </span>DependencyChanged <span style="color: red;">= </span><span style="color: purple;">4</span>,
}

委托的各个参数的含义以及移除原因,在注释中都有明确的解释,我也不再重复了。
我想:有很多人知道Cache的Add, Insert方法有这个参数,也知道有这个委托,但是,它们有什么用呢? 在后面的二个小节中,我将提供二个示例来演示这一强大的功能。

通常,我们会以下面这种方式从Cache中获取结果:

<span style="color: rgb(43, 145, 175);">RunOptions </span>options <span style="color: red;">= </span><span style="color: rgb(43, 145, 175);">HttpRuntime</span><span style="color: red;">.</span>Cache[RunOptionsCacheKey] <span style="color: blue;">as </span><span style="color: rgb(43, 145, 175);">RunOptions</span>;
<span style="color: blue;">if</span>( options <span style="color: red;">== </span><span style="color: blue;">null </span>) {
    <span style="color: green;">// 缓存中没有,则从文件中加载
    // ..................................

    </span><span style="color: rgb(43, 145, 175);">HttpRuntime</span><span style="color: red;">.</span>Cache<span style="color: red;">.</span>Insert(RunOptionsCacheKey, options, dep);
}
<span style="color: blue;">return </span>options;

这其实也是一个惯用法了:先尝试从缓存中获取,如果没有,则从数据源中加载,并再次放入缓存。

为什么会在访问Cache时返回null呢?答案无非就是二种原因:1. 根本没有放入Cache,2. 缓存项失效被移除了。
这种写法本身是没有问题,可是,如果从数据源中加载数据的时间较长,情况会怎样呢?
显然,会影响后面第一次的访问请求。您有没有想过,如果缓存项能一直放在Cache中,那不就可以了嘛。 是的,通常来说,只要您在将一个对象放入Cache时,不指定过期时间,不指定缓存依赖,且设置为永不移除,那么对象确实会一直在Cache中, 可是,过期时间和缓存依赖也很有用哦。如何能二者兼得呢?

与 CacheItemRemovedReason 枚举不同,此枚举不包含 Removed 或 Underused 值。可更新的缓存项是不可移除的,因而绝不会被 ASP.NET 自动移除,即使需要释放内存也是如此。

再一次提醒:有时我们确实需要缓存失效这个特性,但是,缓存失效后会被移除。 虽然我们可以让后续的请求在获取不到缓存数据时,从数据源中加载,也可以在CacheItemRemovedCallback回调委托中, 重新加载缓存数据到Cache中,但是在数据的加载过程中,Cache并不包含我们所期望的缓存数据,如果加载时间越长,这种【空缺】效果也会越明显。 这样会影响(后续的)其它请求的访问。为了保证让我们所期望的缓存数据能够一直存在于Cahce中,且仍有失效机制,我们可以使用【移除前通知】功能。

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

我看过一些ASP.NET的书,也看过一些人写的关于Cache方面的文章,基本上,要么是一带而过,要么只是举个毫无实际意义的示例。 可惜啊,这么强大的特性,我很少见到有人把它用起来。

今天,我就举个有实际意义的示例,再现Cache的强大功能!

我有这样一个页面,可以让用户调整(上下移动)某个项目分支记录的上线顺序:

当用户需要调整某条记录的位置时,页面会弹出一个对话框,要求输入一个调整原因,并会发邮件通知所有相关人员。

由于界面的限制,一次操作(点击上下键头)只是将一条记录移动一个位置,当要对某条记录执行跨越多行移动时,必须进行多次移动。 考虑到操作的方便性以及不受重复邮件的影响,程序需要实现这样一个需求: 页面只要求输入一次原因便可以对一条记录执行多次移动操作,并且不要多次发重复邮件,而且要求将最后的移动结果在邮件中发出来。

这个需求很合理,毕竟谁都希望操作简单。

那么如何实现这个需求呢?这里要从二个方面来实现,首先,在页面上我们应该要完成这个功能,对一条记录只弹一次对话框。 由于页面与服务端的交互全部采用Ajax方式进行(不刷新),状态可以采用JS变量来维持,所以这个功能在页面中是很容易实现。 再来看一下服务端,由于服务端并没有任何状态,当然也可以由页面把它的状态传给服务端,但是,哪次操作是最后一次呢? 显然,这是无法知道的,最后只能修改需求,如果用户在2分钟之内不再操作某条记录时,便将最近一次操作视为最后一次操作。

基于新的需求,程序必须记录用户的最近一次操作,以便在2分钟不操作后,发出一次邮件,但要包含第一次输入的原因, 还应包含最后的修改结果哦。

该怎么实现这个需求呢? 我立即就想到了ASP.NET Cache,因为我了解它,知道它能帮我完成这个功能。下面我来说说在服务端是如何实现的。

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

为了便于理解,我特意为大家准备了一个示例。整个示例由三部分组成:一个页面,一个JS文件,服务端代码。先来看页面代码: 

页面的显示效果如下:

处理页面中二个按钮的JS代码如下: 

<span style="color: green;">// 用户输入的调整记录的原因
</span><span style="color: blue;">var </span>g_reason <span style="color: red;">= </span><span style="color: blue;">null</span>;

$(<span style="color: blue;">function</span>(){
    $(<span style="color: rgb(163, 21, 21);">"#btnMoveUp"</span>).click( <span style="color: blue;">function</span>() { MoveRec(<span style="color: red;">-</span><span style="color: purple;">1</span>); } );
    $(<span style="color: rgb(163, 21, 21);">"#btnMoveDown"</span>).click( <span style="color: blue;">function</span>() { MoveRec(<span style="color: purple;">1</span>); } );
});

<span style="color: blue;">function </span>MoveRec(direction){
    <span style="color: blue;">if</span>( <span style="color: red;">~~</span>($(<span style="color: rgb(163, 21, 21);">"#spanSequence"</span>).text()) <span style="color: red;">+ </span>direction <span style="color: red;">< </span><span style="color: purple;">0 </span>){
        alert(<span style="color: rgb(163, 21, 21);">"已经不能上移了。"</span>);
        <span style="color: blue;">return</span>;
    }
    <span style="color: blue;">if</span>( g_reason <span style="color: red;">== </span><span style="color: blue;">null </span>){
        g_reason <span style="color: red;">= </span>prompt(<span style="color: rgb(163, 21, 21);">"请输入调整记录顺序的原因:"</span><span style="color: red;">, </span><span style="color: rgb(163, 21, 21);">"由于什么什么原因,我要调整..."</span>);
        <span style="color: blue;">if</span>( g_reason <span style="color: red;">== </span><span style="color: blue;">null </span>)
            <span style="color: blue;">return</span>;
    }
    
    $.ajax({
        url<span style="color: red;">: </span><span style="color: rgb(163, 21, 21);">"/AjaxDelaySendMail/MoveRec.fish"</span><span style="color: red;">,
        </span>data<span style="color: red;">: </span>{ RowGuid<span style="color: red;">: </span>$(<span style="color: rgb(163, 21, 21);">"#spanRowGuid"</span>).text()<span style="color: red;">, 
                </span>Direction<span style="color: red;">: </span>direction<span style="color: red;">,
                </span>Reason<span style="color: red;">: </span>g_reason
        }<span style="color: red;">,
        </span>type<span style="color: red;">: </span><span style="color: rgb(163, 21, 21);">"POST"</span><span style="color: red;">, </span>dataType<span style="color: red;">: </span><span style="color: rgb(163, 21, 21);">"text"</span><span style="color: red;">,
        </span>success<span style="color: red;">: </span><span style="color: blue;">function</span>(responseText){
            $(<span style="color: rgb(163, 21, 21);">"#spanSequence"</span>).text(responseText);
        }
    });
}

说明:在服务端,我使用了我在【用Asp.net写自己的服务框架】那篇博客中提供的服务框架, 服务端的全部代码是这个样子的:(注意代码中的注释) 

<span style="color: gray;">/// <summary>
/// </span><span style="color: green;">移动记录的相关信息。
</span><span style="color: gray;">/// </summary>
</span><span style="color: blue;">public class </span><span style="color: rgb(43, 145, 175);">MoveRecInfo
</span>{
    <span style="color: blue;">public string </span>RowGuid;
    <span style="color: blue;">public int </span>Direction;
    <span style="color: blue;">public string </span>Reason;
}


[<span style="color: rgb(43, 145, 175);">MyService</span>]
<span style="color: blue;">public class </span><span style="color: rgb(43, 145, 175);">AjaxDelaySendMail
</span>{
    [<span style="color: rgb(43, 145, 175);">MyServiceMethod</span>]
    <span style="color: blue;">public int </span>MoveRec(<span style="color: rgb(43, 145, 175);">MoveRecInfo </span>info)
    {
        <span style="color: green;">// 这里就不验证从客户端传入的参数了。实际开发中这个是必须的。

        // 先来调整记录的顺序,示例程序没有数据库,就用Cache来代替。
        </span><span style="color: blue;">int </span>sequence <span style="color: red;">= </span><span style="color: purple;">0</span>;
        <span style="color: blue;">int</span><span style="color: red;">.</span>TryParse(<span style="color: rgb(43, 145, 175);">HttpRuntime</span><span style="color: red;">.</span>Cache[info<span style="color: red;">.</span>RowGuid] <span style="color: blue;">as string</span>, <span style="color: blue;">out </span>sequence);
        <span style="color: green;">// 简单地示例一下调整顺序。
        </span>sequence <span style="color: red;">+= </span>info<span style="color: red;">.</span>Direction;
        <span style="color: rgb(43, 145, 175);">HttpRuntime</span><span style="color: red;">.</span>Cache[info<span style="color: red;">.</span>RowGuid] <span style="color: red;">= </span>sequence<span style="color: red;">.</span>ToString();


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

        <span style="color: blue;">return </span>sequence;
    }    

    <span style="color: blue;">private void </span>MoveRecInfoRemovedCallback(<span style="color: blue;">string </span>key, <span style="color: blue;">object </span>value, <span style="color: rgb(43, 145, 175);">CacheItemRemovedReason </span>reason)
    {
        <span style="color: blue;">if</span>( reason <span style="color: red;">== </span><span style="color: rgb(43, 145, 175);">CacheItemRemovedReason</span><span style="color: red;">.</span>Removed )
            <span style="color: blue;">return</span>;        <span style="color: green;">// 忽略后续调用HttpRuntime.Cache.Insert()所触发的操作

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

        // 从参数value取回操作信息
        </span><span style="color: rgb(43, 145, 175);">MoveRecInfo </span>info <span style="color: red;">= </span>(<span style="color: rgb(43, 145, 175);">MoveRecInfo</span>)value;
        <span style="color: green;">// 这里可以对info做其它的处理。

        // 最后发一次邮件。整个延迟发邮件的过程就处理完了。
        </span><span style="color: rgb(43, 145, 175);">MailSender</span><span style="color: red;">.</span>SendMail(info);
    }
}

为了能让JavaScript能直接调用C#中的方法,还需要在web.config中加入如下配置:

<span style="color: blue;"><</span><span style="color: rgb(163, 21, 21);">httpHandlers</span><span style="color: blue;">>
    <</span><span style="color: rgb(163, 21, 21);">add </span><span style="color: red;">path</span><span style="color: blue;">=</span>"<span style="color: blue;">*.fish</span>" <span style="color: red;">verb</span><span style="color: blue;">=</span>"<span style="color: blue;">*</span>" <span style="color: red;">validate</span><span style="color: blue;">=</span>"<span style="color: blue;">false</span>" <span style="color: red;">type</span><span style="color: blue;">=</span>"<span style="color: blue;">MySimpleServiceFramework.AjaxServiceHandler</span>"<span style="color: blue;">/>
</</span><span style="color: rgb(163, 21, 21);">httpHandlers</span><span style="color: blue;">></span>

好了,示例代码就是这些。如果您有兴趣,可以在本文的结尾处下载这些示例代码,自己亲自感受一下利用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中获取。 如果配置参数较多,这种做法或许也会影响性能,毕竟配置参数并不会经常修改,如果能直接访问一个静态变量就能获取到,应该会更快。 通常,我们可能会这样做:

<span style="color: blue;">private static </span><span style="color: rgb(43, 145, 175);">RunOptions </span>s_RunOptions;

<span style="color: blue;">public static </span><span style="color: rgb(43, 145, 175);">RunOptions </span>RunOptions
{
    <span style="color: green;">// s_RunOptions 的初始化放在Init方法中了,会在Global.asax的Application_Start事件中调用。
    </span><span style="color: blue;">get </span>{ <span style="color: blue;">return </span>s_RunOptions; }
}

<span style="color: blue;">public static </span><span style="color: rgb(43, 145, 175);">RunOptions </span>LoadRunOptions()
{
    <span style="color: blue;">string </span>path <span style="color: red;">= </span><span style="color: rgb(43, 145, 175);">Path</span><span style="color: red;">.</span>Combine(AppDataPath, <span style="color: rgb(163, 21, 21);">"RunOptions.xml"</span>);
    <span style="color: blue;">return </span>RwConfigDemo<span style="color: red;">.</span><span style="color: rgb(43, 145, 175);">XmlHelper</span><span style="color: red;">.</span>XmlDeserializeFromFile<span style="color: red;"><</span><span style="color: rgb(43, 145, 175);">RunOptions</span><span style="color: red;">></span>(path, <span style="color: rgb(43, 145, 175);">Encoding</span><span style="color: red;">.</span>UTF8);
}

但是,这种做法有一缺点就是:不能在配置文件更新后,自动加载最新的配置结果。

为了解决这个问题,我们可以使用Cache提供的文件依赖以及移除通知功能。 前面的示例演示了移除后通知功能,这里我再演示一下移除前通知功能。
说明:事实上,完成这个功能,可以仍然使用移除后通知,只是移除前通知我还没有演示,然而,这里使用移除前通知并没有显示它的独有的功能。

下面的代码演示了在配置文件修改后,自动更新运行参数的实现方式:(注意代码中的注释)

<span style="color: blue;">private static int </span>s_RunOptionsCacheDependencyFlag <span style="color: red;">= </span><span style="color: purple;">0</span>;

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

    <span style="color: blue;">int </span>flag <span style="color: red;">= </span>System<span style="color: red;">.</span>Threading<span style="color: red;">.</span><span style="color: rgb(43, 145, 175);">Interlocked</span><span style="color: red;">.</span>CompareExchange(<span style="color: blue;">ref </span>s_RunOptionsCacheDependencyFlag, <span style="color: purple;">1</span>, <span style="color: purple;">0</span>);

    <span style="color: green;">// 确保只调用一次就可以了。
    </span><span style="color: blue;">if</span>( flag <span style="color: red;">== </span><span style="color: purple;">0 </span>) {
        <span style="color: green;">// 让Cache帮我们盯住这个配置文件。
        </span><span style="color: rgb(43, 145, 175);">CacheDependency </span>dep <span style="color: red;">= </span><span style="color: blue;">new </span><span style="color: rgb(43, 145, 175);">CacheDependency</span>(path);
        <span style="color: rgb(43, 145, 175);">HttpRuntime</span><span style="color: red;">.</span>Cache<span style="color: red;">.</span>Insert(RunOptionsCacheKey, <span style="color: rgb(163, 21, 21);">"Fish Li"</span>, dep,
            <span style="color: rgb(43, 145, 175);">Cache</span><span style="color: red;">.</span>NoAbsoluteExpiration, <span style="color: rgb(43, 145, 175);">Cache</span><span style="color: red;">.</span>NoSlidingExpiration, RunOptionsUpdateCallback);
    }

    <span style="color: blue;">return </span>options;
}

<span style="color: blue;">public static void </span>RunOptionsUpdateCallback(
    <span style="color: blue;">string </span>key, <span style="color: rgb(43, 145, 175);">CacheItemUpdateReason </span>reason, 
    <span style="color: blue;">out object </span>expensiveObject, 
    <span style="color: blue;">out </span><span style="color: rgb(43, 145, 175);">CacheDependency </span>dependency, 
    <span style="color: blue;">out </span><span style="color: rgb(43, 145, 175);">DateTime </span>absoluteExpiration, 
    <span style="color: blue;">out </span><span style="color: rgb(43, 145, 175);">TimeSpan </span>slidingExpiration)
{
    <span style="color: green;">// 注意哦:在这个方法中,不要出现【未处理异常】,否则缓存对象将被移除。

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

    </span>expensiveObject <span style="color: red;">= </span><span style="color: rgb(163, 21, 21);">"http://www.cnblogs.com/fish-li/"</span>;
    dependency <span style="color: red;">= </span><span style="color: blue;">new </span><span style="color: rgb(43, 145, 175);">CacheDependency</span>(<span style="color: rgb(43, 145, 175);">Path</span><span style="color: red;">.</span>Combine(AppDataPath, <span style="color: rgb(163, 21, 21);">"RunOptions.xml"</span>));
    absoluteExpiration <span style="color: red;">= </span><span style="color: rgb(43, 145, 175);">Cache</span><span style="color: red;">.</span>NoAbsoluteExpiration;
    slidingExpiration <span style="color: red;">= </span><span style="color: rgb(43, 145, 175);">Cache</span><span style="color: red;">.</span>NoSlidingExpiration;

    <span style="color: green;">// 重新加载配置参数
    </span>s_RunOptions <span style="color: red;">= </span>LoadRunOptions();
}

改动很小,只是LoadRunOptions方法做了修改了而已,但是效果却很酷。

还记得我在上篇博客【在.net中读写config文件的各种方法】的结尾处留下来的问题吗? 这个示例就是我的解决方案。

文件监视技术的选择

对于文件监视,我想有人或许会想到FileSystemWatcher。正好我就来说说关于【文件监视技术】的选择问题。
说明,本文所有结论均为我个人的观点,仅供参考。

这个组件,早在做WinForm开发时就用过了,对它也是印象比较深的。
它有一个包装不好的地方是:事件会重复发出。比如:一次文件的保存操作,它却引发了二次事件。
什么,你不信? 正好,我还准备了一个示例程序。

说明:图片中显示了发生过二次事件,但我只是在修改了文件后,做了一次保存操作而已。 本文的结尾处有我的示例程序,您可以自己去试一下。这里为了方便,还是贴出相关代码: 

<span style="color: blue;">private void </span>Form1_Shown(<span style="color: blue;">object </span>sender, <span style="color: rgb(43, 145, 175);">EventArgs </span>e)
{
    <span style="color: blue;">this</span><span style="color: red;">.</span>fileSystemWatcher1<span style="color: red;">.</span>Path <span style="color: red;">= </span><span style="color: rgb(43, 145, 175);">Environment</span><span style="color: red;">.</span>CurrentDirectory;
    <span style="color: blue;">this</span><span style="color: red;">.</span>fileSystemWatcher1<span style="color: red;">.</span>Filter <span style="color: red;">= </span><span style="color: rgb(163, 21, 21);">"RunOptions.xml"</span>;
    <span style="color: blue;">this</span><span style="color: red;">.</span>fileSystemWatcher1<span style="color: red;">.</span>NotifyFilter <span style="color: red;">= </span>System<span style="color: red;">.</span>IO<span style="color: red;">.</span><span style="color: rgb(43, 145, 175);">NotifyFilters</span><span style="color: red;">.</span>LastWrite;
    <span style="color: blue;">this</span><span style="color: red;">.</span>fileSystemWatcher1<span style="color: red;">.</span>EnableRaisingEvents <span style="color: red;">= </span><span style="color: blue;">true</span>;            
}

<span style="color: blue;">private void </span>fileSystemWatcher1_Changed(<span style="color: blue;">object </span>sender, System<span style="color: red;">.</span>IO<span style="color: red;">.</span><span style="color: rgb(43, 145, 175);">FileSystemEventArgs </span>e)
{
    <span style="color: blue;">string </span>message <span style="color: red;">= </span><span style="color: blue;">string</span><span style="color: red;">.</span>Format(<span style="color: rgb(163, 21, 21);">"{0} {1}."</span>, e<span style="color: red;">.</span>Name, e<span style="color: red;">.</span>ChangeType);
    <span style="color: blue;">this</span><span style="color: red;">.</span>listBox1<span style="color: red;">.</span>Items<span style="color: red;">.</span>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集合不具备的。 因此,合理地同时使用它们,会让程序有着最好的性能,也同时拥有更强大的功能。

什么是cache?为什么需要cache?cache存在的合理性 文章目录为什么需要cache?为什么从cache里面读取就能提高cpu的数据处理效率?cache存在的合理性程序的局部性原理什么是cache?cache的工作流程如何写出提高 Cache 命中率的代码?结尾 为什么需要cache? ..这里用一句话概括,CPU的数据处理速度,远远高于从内存中读取数据的速度,也就是说,如果CPU直接从内存中读取数据进行处理,CPU很长一段时间会用在等待数据读取完毕,这显示十分浪费CPU资源的。所以通过cache来缓存一部分数据,CPU读取数据时,先从cache里面找,从而提 阅读详情

相关推荐

cache基础解析

cache 是一个放置在处理器和主存之间的小容量高速存储器阵列。它是一个存储部分最近访问的主存内容的缓冲。相比于系统存储器,处理器在可能的情况下更优先使用cache存储器,以改善系统的平均性能。写缓冲器是一个位于处理器内核与主存之间的非常小的先进先出(FIFO)存储器,它可以把处理器内核与cache存储器从低速的主存写操作中解脱出来。局部性原理说明,程序在执行过程中会频繁运行小范围的循环代码,而这些代码会对数据存储器中的局部数据反复访问。它解释了为什么使用带cache的内核后,系统的平均性能会显著改善。

weixin_55953651的博客 5399

磁盘缓存和内存缓存的区别

内存缓存高速缓存(英语:cache,英语发音:/kæʃ/ kash [1][2][3],简称缓存),其原始意义是指访问速度比一般随机存取存储器(RAM)快的一种RAM,通常它不像系统主存那样使用DRAM技术,而使用昂贵但较快速的SRAM技术。原理 Cache一词来源于1967年的一篇电子工程期刊论文。其作者将法语词“cache”赋予“safekeeping storage”的涵义,用于电脑工程领域

Android developer. 2万+

SAP采购含税价转换的底层逻辑解析:ABAP例程如何实现价税分离计算

本文深入解析SAP系统中采购含税价与不含税价的转换机制,重点介绍VOFM例程和ABAP代码实现价税分离计算的方法。通过详细讲解数学原理、税率体系及SAP定价引擎架构,为开发人员提供完整的解决方案,帮助提升采购业务中的价格处理效率和准确性。

weixin_29237635的博客 451

cache.get方法的两个参数:key,Callable对象

cache.get方法的两个参数key,Callable对象

weixin_44587820的博客 3529

【VisualStdio】手把手教你做一个上位机(1)控件设计布局

网上有很多形形色色的制作上位机教程,有些写的挺好的,但是过于单调。

m0_73409202的博客 5438

永磁同步电机无传感器控制:HFI与ESMO技术详解

高频注入(HFI)和增强型滑模观测器(ESMO)是现代永磁同步电机(PMSM)无传感器控制的核心技术。HFI通过注入高频信号利用电机凸极效应获取转子位置,而ESMO则通过改进传统滑模观测器,有效抑制抖振并提升动态响应。这两种技术的结合特别适用于零低速区域的位置估计,在工业伺服、电动汽车等领域有广泛应用。方波高频注入作为实现方案之一,具有计算量小、实现简单的特点,配合自适应滤波等信号处理技术,可以在保证精度的同时降低系统成本。

weixin_29183637的博客 356

一起谈.NET技术,ASP.NET缓存分析和实践浅析

  说到ASP.NET缓存,那就是:尽早缓存;经常接触缓存的您应该在应用程序的每一层都实现缓存。向数据层、业务逻辑层、UI或输出层添加缓存支持。内存现在非常便宜—因此,通过以智能的方式在整个应用程序中实现缓存,可以获得很大的性能提高。缓存可以掩盖许多过失缓存是一种无需大量时间和分析就可以获得“足够良好的”性能的方法。   这里再次强调,内存现在非常便宜,因此,如果您能通过将输出缓存30秒,而不是花...

weixin_34144450的博客 104

BMS短路测试避坑指南:从炸管到稳定,我是如何搞定MOS管和TVS的

本文详细记录了BMS短路测试中MOS管和TVS的优化过程,从驱动电阻调整、PCB寄生参数控制到TVS选配策略,提供了一套实用的调试方法论。通过实战案例解析,帮助工程师避免常见陷阱,实现稳定可靠的短路测试性能。

weixin_30652897的博客 140

ASP.NET 缓存技术(一)——启用页面输出缓存

本节初步揭开了 ASP.NET 缓存技术的面纱,介绍了开启 ASPX 页面输出缓存的基本方法,但这不是本节的重点,最需要关注的是 ASP.NET 缓存技术的实现细节,“页面输出缓存的本质”小节中对此有了说明,但限于篇幅和作者的水平,并不详尽。大家可以多多实践,并使用 Fiddler 这样的 HTTP 嗅探工具来深入研究这些细节。

Athrun Saga's Thinking 1654

车牌识别系统

随这图形图像技术的发展,现在的车牌识别技术准确率越来越高,识别速度越来越快。无论何种形式的车牌识别系统,它们都是由触发、图像采集、图像识别模块、辅助光源和通信模块组成的。车牌识别系统涉及光学、电器、电子控制、数字图像处理、计算视觉、人工智能等多项技术。触发模块负责在车辆到达合适位置时,给出触发信号,控制抓拍。辅助光源提供辅助照明,保证系统在不同的光照条件下都能拍摄到高质量的图像。图像预处理程序对抓拍的图像进行处理,去除噪声,并进行参数调整。然后通过车牌定位、字符识别,最后将识别结果输出。 牌照自动识别是一

FPGA/MATLAB学习教程/源码/项目合作开发 2万+

从“能跑”到“好用”:一名 Python 开发者的工程化进阶之路【Python心得篇】

Python 早已不只是写脚本、跑数据的工具。后端服务AI Agent 编排层自动化平台核心语言数据与业务之间的“中枢层”区别只在于:你是用脚本方式,还是工程方式在写。如果你已经能写“能跑的 Python”,下一步,就是让它长期可跑、稳定可演进。Python 的真正价值不在于“写得快”,而在于能否支撑持续演进的工程实践。当代码从一次性脚本走向长期运行的系统,开发者必须从结构设计、职责拆分、性能意识和类型约束等方面进行系统性思考。

博客 1548

.Net环境下的缓存技术介绍

http://www.cnblogs.com/Abac/archive/2004/02/11/1166.aspx 摘要: 介绍缓存的基本概念和常用的缓存技术,给出了各种技术的实现机制的简单介绍和适用范围说明,以及设计缓存方案应该考虑的问题(共17页) 1         概念 1.1   缓存能解决的问题 · 性能——将相应数据存储起来以避免数据的重复创建、处理和传输,可有效提高性能。

tiantian1980的专栏 6606

【PINN物理信息网络】基于PINN物理信息网络结合Wave 2D有限差分方法(python)

【PINN物理信息网络】基于PINN物理信息网络结合Wave 2D有限差分方法(python)

k8291121的博客 1819

asp.net有效使用缓存(转)

Rob HowardTelligent Systems, Inc. 标题比较口语化,但是引起了您的兴趣,对不对?ASP.NET 缓存是到目前为止我最喜欢的 ASP.NET 功能之一。为什么呢?这是因为,通过使用缓存,可以获得一些奇异的性能和可伸缩性结果,而这些结果可以轻松地进行衡量并转换为应用程序实际节省的资金。这会使您成为 CTO 最喜欢的代码猴子,因为您影响了至关重要的应用程序投...

HisSystem2088的博客 160

YOLO11改进-注意力-引入自调制特征聚合模块SMFA

SMFA(自调制特征聚合模块): SMFA模块用于协同建模局部和非局部信息,它分为两个分支:一个是EASA(Efficient Approximation of Self-Attention,简化的自注意力分支),用于捕获非局部信息;另一个是LDE(Local Detail Estimation,局部细节估计分支),用于捕获局部细节。EASA通过对输入特征进行下采样,然后利用全局特征的方差进行调制,再与原始特征进行聚合,提取非局部结构信息。LDE分支则通过卷积操作提取输入特征中的高频局部信息。

qq_64693987的博客 2208

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

C#.Net环境下的缓存技术

一、缓存技术本节将介绍以下技术: 使用Asp.Net缓存; 使用Remoting Singleton缓存; 使用内存映射文件; 使用SQL Server缓存; 使用静态变量缓存; 使用Asp.net 会话状态(Session State); 使用Asp.net客户端缓存和状态; 使用Internet Explorer缓存。 2.1 Asp.net缓存将常用的数据保存在内存中对as...

weixin_30362801的博客 369

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

.Net环境下的缓存技术介绍 (转)

.Net环境下的缓存技术介绍 (转) 摘要:介绍缓存的基本概念和常用的缓存技术,给出了各种技术的实现机制的简单介绍和适用范围说明,以及设计缓存方案应该考虑的问题(共17页) 1 概念 1.1 缓存能解决的问题 · 性能——将相应数据存储起来以避免数据的重复创建、处理和传输,可有效提高性能。比如将不改变的数据缓存起来,例如国家列表等,这样能明显提高web程序...

weixin_30267785的博客 77

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

ASP.NET与 .NET MVC的认识

asp.net 特性: 母版页 :一套应用到其他页面的模版主题:或皮肤,为应用程序创建定制的外观。Web控件:web服务器控件,web用户控件,web自定义控件即 runat="server,标准控件/数据绑定控件、验证控件/导航控件缓存技术:页面级输出缓存、用户控件级输出缓存(或称为片段缓存)和全部缓存 Viewstate和Ispostback机制asp.net mvc 优点: 分层式结构,...

weixin_33981932的博客 81

asp.net 2.0 缓存(理论篇)

asp.net 2.0 缓存(理论篇)      ASP.NET 2.0提供了一些新的用于提升程序性能的技术特性,其中,缓存技术是非常重要的一个特性,它提供了一种非常好的本地数据缓存机制,可以非常容易的定制属于数据缓从,从而有效的提高数据访问的性能。   缓存有一个不太容易克服的缺点,那就是数据过期的问题。最典型的情况是,如果将数据库表中的数据内容缓存到服务器内存中,当数据库表中的记录发生

打滚dě土豆 专栏 473
上一篇: ASP.NET 管道事件与HttpModule, HttpHandler简单理解
下一篇: 使用 DependentTransaction 管理并发
wingahi
博客等级 码龄15年 15粉丝 43原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值