1. 程式人生 > >利用快取過期在ASP.NET中實現定時器

利用快取過期在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程式,快取定時器(我們把它稱為“快取時鐘

”吧)就可以正常工作,而他的工作是由系統自行維護的,而不是Thread程序維護,所以穩定性會有很大的提高。經過我的實踐,只要伺服器沒有重啟、IIS程序沒有崩潰,上述中採用在緩衝過期時請求Web頁面(WebClient)的行為是不會讓IIS程序自然消亡的(快取過期時間長短、伺服器配置也會影響)。(下劃線文字的描述與事實不符,觀測的結果可能受到網路蜘蛛的影響)具體如何實現,請看下文。

複製程式碼

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
        {

           //除錯的時候注意是否對該檔案有寫許可權

        }
    }
} 

  1. 新增快取:RegistCacheTimmer
  2. 請求頁面:RequestWebPage
  3. 執行任務:ExeJob
  4. 快取過期時候執行請求頁、執行任務:CacheItemOnRemoved
  5. 寫日誌的方法:WriteCacheLog

然後在Global.asax檔案中新增下列程式碼段:

複製程式碼
   void Application_BeginRequest(Object sender, EventArgs e)
    {        
        CacheTimer registTimer = new CacheTimer();
        registTimer.RegistCacheTimmer();        
    }
但是如果我們的任務在沒有執行完畢,IIS程序崩潰或者伺服器重啟了,怎麼辦?我的建議是把任務寫人檔案(包括資料庫),檢查的時候直接檢查任務檔案就可以保證重啟IIS程序,任務不丟失了。