ASP.NET 從最早期的版本就實做了一套好用的快取機制(System.Web.Caching.Cache),一直以來任何非 ASP.NET 的應用程式 (例如 WinForm, WPF, Console, WinService, …) 若要使用快取機制都必須將System.Web.dll 參考進專案才能使用,但從 .NET 4.0 開始出現了另一個擴充性更強的快取機制,稱為Object Caching (物件快取) 機制,未來這兩套快取機制將各司其職、相輔相成。


System.Runtime.Caching.dll,此組件非常的小,我用 NDepend 工具分析了一下此組件,總共 IL 指令集僅有 9,844 個而已,而 System.Web.dll 的 IL 指令集總共有 496,395 個之多,就算只計算System.Web.Caching 命名空間的 IL 也有 14,760 個,還是比這次新增的 System.Runtime.Caching.dll來的大。

備註:原本的 System.Web.Caching.Cache 類別依然存在,功能與之前一樣,還是可以正常使用。

System.Runtime.Caching.dll 組件在 System.Runtime.Caching 命名空間

  • 透過一組抽象類別可讓你自行實做任意物件快取機制,例如: 實做分散式快取機制。
  • 透過此抽象類別實做的記憶體快取機制類別System.Runtime.Caching.MemoryCache 類別

如果你曾經用過 ASP.NET Cache 物件,因為兩者的相似性非常高,所以要上手使用 MemoryCache 那可是非常容易,以下是 MemoryCache 的使用範例 ( 以 Windows Form 做範例 ):

1. private void btnGet_Click(object sender, EventArgs e)   
2. {   
3. //Obtain a reference to the default MemoryCache instance.   
4. //Note that you can create multiple MemoryCache(s) inside   
5. //of a single application.   
6.   ObjectCache cache = MemoryCache.Default;   
7.     
8. //In this example the cache is storing the contents of a file string   
9. "filecontents"] as string;  
10.     
11. //If the file contents are not currently in the cache, then   
12. //the contents are read from disk and placed in the cache.   
13. if (fileContents == null)   
14.   {  
15. //A CacheItemPolicy object holds all the pieces of cache   
16. //dependency and cache expiration metadata related to a single   
17. //cache entry.   
18. new CacheItemPolicy();   
19.       
20. //Build up the information necessary to create a file dependency.   
21. //In this case we just need the file path of the file on disk.   
22. new List();   
23. "c:\\data.txt");   
24.       
25. //In the new cache API, dependencies are called "change monitors".   
26. //For this example we want the cache entry to be automatically expired   
27. //if the contents on disk change. A HostFileChangeMonitor provides   
28. //this functionality.   
29. new HostFileChangeMonitor(filePaths));   
30.       
31. //Fetch the file's contents   
32. "c:\\data.txt");   
33.       
34. //And then store the file's contents in the cache   
35. "filecontents", fileContents, policy);   
36.       
37.   }   
38.   MessageBox.Show(fileContents);   
39. }

System.Web.Caching 中內建的 Cache 與 System.Runtime.Caching 的 MemoryCache 有幾點需注意:

  • System.Runtime.Caching.dll 組件下的 MemoryCache 類別與 System.Web.dll 組件下的 Cache類別完全沒有相依性,兩者是完全獨立的組件,任何非 ASP.NET 應用程式都不應該載入System.Web.dll
  • ASP.NET 的 Cache 與 MemoryCache 雖然都是記憶體快取(In-memory cache),但在 ASP.NET 中只能使用一份 Cache 物件,而在 MemoryCache 可在 AppDomain 中建立多份快取物件。

像我們之前在開發大型網站時,由於 ASP.NET 快取資料只能儲存在記憶體中,這對於大型網站來說非常不實用,甚至於”不能用”,有了 ObjectCache 機制這才出現了一絲生機,這時你就可以在 ASP.NET 中透過 ObjectCache 機制載入自訂的快取機制 (例如: VelocityMemcachedScaleOut, … ) 來加強網站的延展性 (Scalability),相信此功能對擁有大量快取需求的開發人員來說絕對是一大福音。

.NET 4.0新增了一個System.Runtime.Caching命名空間,該命名空間主要提供了一個可擴充的資料快取框架,提供開發者使用與實作快取的功能。

在以往我們可能得自行將資料Load到記憶體中,在背景去監控是否變動或是過期需要更新,甚至是自行撰寫Cache Pool去做控管,甚至是套用LRU演算法去釋放,避免快取佔用過多的記憶體。在.NET 4.0以後,再也不用那麼麻煩了。

框架中比較重要的組成元件有ObjectCache、CacheItemPolicy、ChangeMonitor。

ObjectCache為記憶體快取框架中的主要類別,簡單的說就是一個快取池。提供存取快取所需方法和屬性,支援加入、擷取與更新快取資料,主要職責為建立管理快取的元素、指定快取回收與到期的資訊、觸發變更事件。快取框架中所提供的MemoryCache類別即為ObjectCache抽象類別的實作,可將從快取介質所讀出的資料快取到記憶體中,有點類似於ASP.NET中的Cache類別。

 

CacheItemPolicy的主要職責為告知ObjectCache內的內容何時會過期,我們可設定絕對的或是相對應的過期時間,也可與ChangeMonitor搭配使用。

在快取框架的使用上我們需先將System.Runtime.Caching.dll組件加入參考,該框架不支援Client Profile的.NET Framework,若看不到請切回.NET Framework 4。

並加入System.Runtime.Caching命名空間。

using System.Runtime.Caching;

接著我們遵循著一定的開發Flow就可以了。首先記得要初始ObjectCache,這邊多半是直接取得內建的MemoryCache實體,然後我們要帶入Content的Key,試圖從ObjectCache取得快取的內容,如果有快取的內容就直接回傳,若無則我們需從介質中取得內容,設定CacheItemPolicy,將Content的Key、Content、以及CacheItemPolicy設定給ObjectCachem。

這邊實際的示範個完整的範例

01
using System;
 
       
02
using System.Collections.Generic;
 
       
03
using System.Diagnostics;
 
       
04
using System.Linq;
 
       
05
using System.Runtime.Caching;
 
       
06
using System.Text;
 
       
07
using System.Threading;
 
       
08
using System.Threading.Tasks;
 
       
09
  
 
       
10
namespace CacheItemPolicyDemo
 
       
11
 {
 
       
12
 class Program
 
       
13
 {
 
       
14
 staticvoid Main(string[] args)
 
       
15
 {
 
       
16
 ContentProvider textFile = new ContentProvider();
 
       
17
 Stopwatch sw = new Stopwatch();
 
       
18
 while (true)
 
       
19
 {
 
       
20
 sw.Reset();
 
       
21
 sw.Start();
 
       
22
 Console.WriteLine(DateTime.Now.ToString());
 
       
23
 Console.WriteLine(textFile.Content);
 
       
24
 sw.Stop();
 
       
25
 Console.WriteLine("Elapsed Time: {0} ms", sw.ElapsedMilliseconds);
 
       
26
 Console.WriteLine(newstring('=', 50));
 
       
27
 Console.ReadLine();
 
       
28
 }
 
       
29
 }
 
       
30
 }
 
       
31
  
 
       
32
 publicclass ContentProvider
 
       
33
 {
 
       
34
 public String Content
 
       
35
 {
 
       
36
 get
 
       
37
 {
 
       
38
 conststring CACHE_KEY = "Content";
 
       
39
 string content = m_Cache[CACHE_KEY] asstring;
 
       
40
 if (content == null)
 
       
41
 {
 
       
42
 CacheItemPolicy policy = new CacheItemPolicy();
 
       
43
 policy.AbsoluteExpiration = DateTime.Now.AddMilliseconds(1500);
 
       
44
 //policy.SlidingExpiration = TimeSpan.FromMilliseconds(1500);
 
       
45
  
 
       
46
 //policy.UpdateCallback = new CacheEntryUpdateCallback((args) =>
 
       
47
 //{
 
       
48
 //  Console.WriteLine("MyCachedItemUpdatedCallback...");
 
       
49
 //  Console.WriteLine(string.Format("Remove {0}...Because {1}",args.Key,args.RemovedReason.ToString()));
 
       
50
 //  Console.WriteLine(new string('=', 50));
 
       
51
 //});
 
       
53
 //policy.RemovedCallback = new CacheEntryRemovedCallback((args) =>
 
       
54
 //{
 
       
55
 //    Console.WriteLine("CacheEntryRemovedCallback...");
 
       
56
 //    Console.WriteLine(string.Format("Remove {0}...Because {1}", args.CacheItem.Key, args.RemovedReason.ToString()));
 
       
57
 //    Console.WriteLine(new string('=', 50));
 
       
58
 //});
 
       
59
  
 
       
60
 content = Guid.NewGuid().ToString();
 
       
61
 Thread.Sleep(1000);
 
       
62
 m_Cache.Set(CACHE_KEY, content, policy);
 
       
63
 }
 
       
64
 return content;
 
       
65
 }
 
       
66
 }
 
       
67
  
 
       
68
  
 
       
69
 private ObjectCache _cache;
 
       
70
 private ObjectCache m_Cache
 
       
71
 {
 
       
72
 get
 
       
73
 {
 
       
74
 if (_cache == null)
 
       
75
 _cache = MemoryCache.Default;
 
       
76
 return _cache;
 
       
77
 }
 
       
78
 }
 
       
79
 }
 
       
80
 }

 

以這例子來說,若是CacheItemPolicy設定了AbsoluteExpiration,像是下面這樣:

policy.AbsoluteExpiration = DateTime.Now.AddMilliseconds(1500);



 

則快取的內容會在指定的時間內失效,所以我們程式運行起來一直按著Enter,大概每1.5秒就會更新一次內容,1.5秒以內問回來的資料都是在0ms左右。

若是CacheItemPolicy設定了SlidingExpiration,像是下面這樣:

policy.SlidingExpiration = TimeSpan.FromMilliseconds(1500);



 

程式運行起來一直按著Enter,快取的內容都不會更新,但是放開大約1.5秒再次按下,快取的內容就會被更新。 

CacheItemPolicy這邊也可以設定UpdateCallback與RemovedCallback,分別是快取更新前與更新後的回呼,若有需要這邊也可以自行設定去做些動作,像是紀錄LOG或是將資料回存之類的。

 

再來看一個例子,假設今天我想要從檔案中讀取資料做顯示的動作,我希望每次顯示的值都是最新的內容,在以往我們可能需要在每次讀取時重新載入並回傳內容值;或是載入資料後用FileSystemWatcher監視檔案是否有更動,有更動就重新載入。現在有了.NET 4.0你可以將這樣的功能需求透過快取框架來實現,透過MemoryCache去做檔案內容的快取,並透過HostFileChangeMonitor去監控快取介質,在內部告知快取類別所存的快取是否過期,範例程式如下:

 

運行前我們可以先準備如下內容的文字檔案 

運行後可以看到檔案的內容確實被讀出了,這邊可多按幾次Enter讓範例顯示檔案內容,在檔案未變更的狀況下,快取都不算過期,故取得的內容都是快取中的內容,不會耗費時間在讀取檔案內容上。 

接著變更文字檔案內容後存檔 

按下Enter讓範例程式重新讀取,這時快取會自動透過HostFileChangeMonitor判斷到快取過期,因此我們從快取區讀回的值會是Null,整個檔案內容會再重新載入,顯示的內容就會是新的檔案內容。 

除了HostFileChangeMonitor外,若是快取的介質是SQL資料庫,我們也可以換用SqlChangeMonitor,操作上大致雷同,只是建立出SqlChangeMonitor時要帶入對應的SqlDependency,有興趣的可自行參閱.NET 4.0 MemoryCache with SqlChangeMonitor,這邊不多做示範。

若有需要可至CacheItemPolicyDemoHostFileChangeMonitorDemo下載完整的程式範例。