許多做過程序性能優化的人,或者關注過程程序性能的人,應該都使用過各類緩存技術。 而我今天所說的Cache是專指ASP.NET的Cache,我們可以使用HttpRuntime.Cache訪問到的那個Cache,而不是其它的緩存技術。
以前我在【我心目中的Asp.net核心對象】 這篇博客中簡單地提過它,今天我打算為它寫篇專題博客,專門來談談它,因為它實在是太重要了。在這篇博客中, 我不僅要介紹它的一些常見用法,還將介紹它的一些高級用法。 在上篇博客【在.net中讀寫config文件的各種方法】 的結尾處,我給大家留了一個問題,今天,我將在這篇博客中給出一個我認為較為完美的答案。
本文提到的【延遲操作】方法(如:延遲合並寫入數據庫)屬於我的經驗總結,希望大家能喜歡這個思路。
Cache的基本用途
提到Cache,不得不說說它的主要功能:改善程序性能。
ASP.NET是一種動態頁面技術,用ASP.NET技術做出來的網頁幾乎都是動態的,所謂動態是指:頁面的內容會隨着不同的用戶或者持續更新的數據, 而呈現出不同的顯示結果。既然是動態的,那么這些動態的內容是從哪里來的呢?我想絕大多數網站都有自己的數據源, 程序通過訪問數據源獲取頁面所面的數據,然后根據一些業務規則的計算處理,最后變成適合頁面展示的內容。
由於這種動態頁面技術通常需要從數據源獲取數據,並經過一些計算邏輯,最終變成一些HTML代碼發給客戶端顯示。而這些計算過程顯然也是有成本的。 這些處理成本最直接可表現為影響服務器的響應速度,尤其是當數據的處理過程變得復雜以及訪問量變大時,會變得比較明顯。 另一方面,有些數據並非時刻在發生變化,如果我們可以將一些變化不頻繁的數據的最終計算結果(包括頁面輸出)緩存起來, 就可以非常明顯地提升程序的性能,緩存的最常見且最重要的用途就體現在這個方面。 這也是為什么一說到性能優化時,一般都將緩存擺在第一位的原因。 我今天要說到的ASP.NET Cache也是可以實現這種緩存的一種技術。 不過,它還有其它的一些功能,有些是其它緩存技術所沒有的。
Cache的定義
在介紹Cache的用法前,我們先來看一下Cache的定義:(說明:我忽略了一些意義不大的成員) 『點擊此處展開』
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創建的核心過程:『點擊此處展開』
說明:CacheInternal是個內部用的包裝類,Cache的許多操作都要由它來完成。
在上面的代碼中,numSingleCaches的計算過程很重要,如果上面代碼不容易理解,那么請看我下面的示例代碼: 『點擊此處展開』
程序將會輸出:
1,2,4,4,8,8,8,8,16,16,16,16,16,16,16,16,32,32,32,32
CacheMultiple的構造函數如下:『點擊此處展開』
現在您應該明白了吧:CacheSingle其實是ASP.NET內部使用的緩存容器,多個CPU時,它會創建多個緩存容器。
在寫入時,它是如何定位這些容器的呢?請繼續看代碼:『點擊此處展開』
說明:參數中的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有個很強大的功能,那就是緩存依賴。一個緩存項可以依賴於另一個緩存項。 以下示例代碼創建了二個緩存項,且它們間有依賴關系。首先請看頁面代碼: 『點擊此處折疊』
<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>
頁面后台代碼:『點擊此處折疊』
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的值】時,此時將獲取不到緩存項的內容(右圖)。
根據結果並分析代碼,我們可以看出,在創建Key1的緩存項時,我們使用了這種緩存依賴關系:
CacheDependency dep = new CacheDependency(null, new string[] { "key2" });
所以,當我們更新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更新后,被依賴的緩存結果將會自動清除。這樣就徹底地解決了模塊間的緩存數據依賴問題。
緩存項的依賴關系 - 文件依賴
在上篇博客【在.net中讀寫config文件的各種方法】的結尾, 我給大家留了一個問題:
我希望在用戶修改了配置文件后,程序能立刻以最新的參數運行,而且不用重啟網站。
今天我就來回答這個問題,並給出所需的全部實現代碼。
首先,我要說明一點:上次博客的問題,雖然解決方案與Cache的文件依賴有關,但還需與緩存的移除通知配合使用才能完美的解決問題。 為了便於內容的安排,我先使用Cache的文件依賴來簡單的實現一個粗糙的版本,在本文的后續部分再來完善這個實現。
先來看個粗糙的版本。假如我的網站中有這樣一個配置參數類型: 『點擊此處展開』
我可以將它配置在這樣一個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>
再來一個用於顯示運行參數的頁面: 『點擊此處展開』
下面的代碼就可以實現:在XML修改后,瀏覽頁面就能立即看到最新的參數值: 『點擊此處展開』
注意:這里仍然是在使用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是一個枚舉類型,它包含了如下枚舉值: 『點擊此處展開』
說明:當我們調用Cache的Add, Insert方法時,如果不指定CacheItemPriority選項,最終使用Normal所代表的優先級。 如果我們希望將某個可能不太重要的數據放入緩存時,可以指定優先級為Low或者BelowNormal。 如果想讓緩存項在內存不足時,也不會被移除(除非到期或者依賴項有改變),可使用NotRemovable。
顯然,我們可以使用這個特性來控制緩存對內存壓力的影響。 其它的緩存方案,如static Collection + WeakReference也較難實現這樣靈活的控制。
緩存項的移除通知
ASP.NET Cache與一些static變量所實現的緩存效果並不相同,它的緩存項是可以根據一些特定的條件失效的,那些失效的緩存將會從內存中移除。 雖然,某些移除條件並不是由我們的代碼直接解發的,但ASP.NET還是提供一種方法讓我們可以在緩存項在移除時,能通知我們的代碼。
注意哦:ASP.NET Cache支持移除【前】通知 和 移除【后】通知二種通知方式。
我們可以在調用Add, Insert方法時,通過參數onRemoveCallback傳遞一個CacheItemRemovedCallback類型的委托,以便在移除指定的緩存項時, 能夠通知我們。這個委托的定義如下: 『點擊此處展開』
委托的各個參數的含義以及移除原因,在注釋中都有明確的解釋,我也不再重復了。
我想:有很多人知道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支持, 隨之而來的還有一個委托和一個移除原因的枚舉定義: 『點擊此處展開』
注意:CacheItemUpdateReason這個枚舉只有二項。原因請看MSDN的解釋:
與 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代碼如下: 『點擊此處展開』
說明:在服務端,我使用了我在【用Asp.net寫自己的服務框架】那篇博客中提供的服務框架, 服務端的全部代碼是這個樣子的:(注意代碼中的注釋) 『點擊此處展開』
為了能讓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檢查這些已過時的緩存項的時機並不確定,可能會有延遲。
巧用緩存項的移除通知 實現【自動加載配置文件】
在本文的前部分的【文件依賴】小節中,有一個示例演示了:當配置文件更新后,頁面可以顯示最新的修改結果。 在那個示例中,為了簡單,我直接將配置參數放在Cache中,每次使用時再從Cache中獲取。 如果配置參數較多,這種做法或許也會影響性能,畢竟配置參數並不會經常修改,如果能直接訪問一個靜態變量就能獲取到,應該會更快。 通常,我們可能會這樣做:『點擊此處展開』
但是,這種做法有一缺點就是:不能在配置文件更新后,自動加載最新的配置結果。
為了解決這個問題,我們可以使用Cache提供的文件依賴以及移除通知功能。 前面的示例演示了移除后通知功能,這里我再演示一下移除前通知功能。
說明:事實上,完成這個功能,可以仍然使用移除后通知,只是移除前通知我還沒有演示,然而,這里使用移除前通知並沒有顯示它的獨有的功能。
下面的代碼演示了在配置文件修改后,自動更新運行參數的實現方式:(注意代碼中的注釋) 『點擊此處展開』
改動很小,只是LoadRunOptions方法做了修改了而已,但是效果卻很酷。
還記得我在上篇博客【在.net中讀寫config文件的各種方法】的結尾處留下來的問題嗎? 這個示例就是我的解決方案。
文件監視技術的選擇
對於文件監視,我想有人或許會想到FileSystemWatcher。正好我就來說說關於【文件監視技術】的選擇問題。
說明,本文所有結論均為我個人的觀點,僅供參考。
這個組件,早在做WinForm開發時就用過了,對它也是印象比較深的。
它有一個包裝不好的地方是:事件會重復發出。比如:一次文件的保存操作,它卻引發了二次事件。
什么,你不信? 正好,我還准備了一個示例程序。
說明:圖片中顯示了發生過二次事件,但我只是在修改了文件后,做了一次保存操作而已。 本文的結尾處有我的示例程序,您可以自己去試一下。這里為了方便,還是貼出相關代碼:『點擊此處展開』
對於這個類的使用,只想說一點:會引發的事件很多,因此一定要注意過濾。以下引用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集合不具備的。 因此,合理地同時使用它們,會讓程序有着最好的性能,也同時擁有更強大的功能。
from:http://www.cnblogs.com/fish-li/archive/2011/12/27/2304063.html#_label7