利用快取過期在ASP.NET中實現定時器
在B/S結構中要實現定時器(或者說是一個事務)實在不是一件好辦的事。可當你在網上搜索“ASP.NET定時器”的時候,你會發現搜尋結果是如此的多,可這大多數結果中的程式碼健壯性都是那樣的脆弱——沒有考慮諸如IIS程序的自然消亡、IIS程序的故障崩潰、重啟伺服器等等因素。這些不可捕捉的錯誤會讓你的定時器失去定時的功能。可能會有人問,為什麼要用Web程式做計時器呢?自己新增一個Windows服務或者在資料庫中新增一個作業不就解決了麼?可事實上,又有多少人對自己購買的虛擬主機具有如此許可權呢?如果你用的是自己的伺服器或者更高許可權的伺服器,那此文對你的確是多餘了。
我曾經為ASP.NET中的Application_End感到竊喜,以為有了它,以上提及的自然消亡、故障崩潰都可迎刃而解了,可經過自己的實踐發現那個Application_End真的是很無助——你在該程式碼段真的是nothing to do。國內搜尋到的所謂ASP.NET定時器幾乎都是採用System.Thread程序監控實現的,要讓Thread在IIS程序中保持長期(比如1年365天時時刻刻)的穩定性實在是不敢打包票。
最後我在CodeProject上找到一篇文章《Simulate a Windows Service using ASP.NET to run scheduled jobs》(在ASP.NET中模擬Widnows服務執行計劃任務)。在這篇文章中,他利用快取的過期來實現定時器功能。在ASP.NET中,當你新增快取的時候,會有一個快取回撥函式讓你同時加入,表示當快取過期被系統清除的時候,你可以在這個回撥函式中做一些事情。快取回撥函式正是實現ASP.NET定時器的重要基石。假設我們在00:00:00時刻加入一個快取物件,快取的過期時間我們設定為00:02:00,當到達00:02:00或者更提前的時候,快取將被系統回收,當系統通知快取被回收的時候我們再次新增一個快取,如此迴圈往復(快取被新增->過期->再次新增->過期……
但是要想新增快取,必須有使用者請求Web伺服器才能使用Cache。所以,在快取過期的時候,除了檢查是否有事務需要執行外,還要用WebClient(或者其他方法)請求我們的Web頁面,在頁面被請求的時候新增快取。
上面的思路已經基本模擬出了定時器的功能,但對於伺服器重啟、IIS故障仍然沒有解決。《Simulate a Windows Service using ASP.NET to run scheduled jobs》中提到,可以把Google Robots抓取你網頁的頻率設定大一點,當出現上述情況時,以儘快的速度產生Web請求。只要有人(或者機器人)在訪問你的Web程式,快取定時器(我們把它稱為“快取時鐘
複製程式碼 using System; using System.Data; using System.Net; using System.Configuration; using System.Web; using System.Text; using System.Web.Caching; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; public classCacheTimer { const string logPath = @"d:\cacheLogs\cacheLog.txt"; const string cacheKeyName = "myCacheTimer";//快取的名字 const string rawUrl = "http://localhost/CacheTimer/CacheTimmer.aspx";//快取過期時請求的頁面地址 public void RegistCacheTimmer() { if (HttpContext.Current.Cache[cacheKeyName] != null) return; try { HttpContext.Current.Cache.Add(cacheKeyName, '1', null, DateTime.Now.AddSeconds(12), Cache.NoSlidingExpiration, CacheItemPriority.High, new CacheItemRemovedCallback(CacheItemOnRemoved)); WriteCacheLog("快取時鐘註冊成功"); } catch (Exception e) { WriteCacheLog("快取時鐘註冊失敗:" + e.Message); } } protected void CacheItemOnRemoved(string key, object value, CacheItemRemovedReason reason) { if (key == cacheKeyName) { ExeJob(); RequestWebPage(); } } protected void ExeJob() { WriteCacheLog("被執行了"); //如果日期的天數能被7整除、下午13點到13點15分的時候,則執行 if (DateTime.Now.Day % 7 == 0 && DateTime.Now.Hour == 13 && DateTime.Now.Minute <= 15) { //執行資料操作或者其他任務 } } protected void RequestWebPage() { WebClient wc = new WebClient(); wc.DownloadData(rawUrl); } public static void WriteCacheLog(string logInfor) { try { System.IO.StreamWriter sw = new System.IO.StreamWriter(logPath, true, Encoding.GetEncoding("GB2312")); sw.WriteLine(DateTime.Now.ToString() + " " + logInfor); sw.Close(); sw.Dispose(); } catch { //除錯的時候注意是否對該檔案有寫許可權 } } }
- 新增快取:RegistCacheTimmer
- 請求頁面:RequestWebPage
- 執行任務:ExeJob
- 快取過期時候執行請求頁、執行任務:CacheItemOnRemoved
- 寫日誌的方法:WriteCacheLog
然後在Global.asax檔案中新增下列程式碼段:
複製程式碼 void Application_BeginRequest(Object sender, EventArgs e) { CacheTimer registTimer = new CacheTimer(); registTimer.RegistCacheTimmer(); }但是如果我們的任務在沒有執行完畢,IIS程序崩潰或者伺服器重啟了,怎麼辦?我的建議是把任務寫人檔案(包括資料庫),檢查的時候直接檢查任務檔案就可以保證重啟IIS程序,任務不丟失了。