摘要:續上集,使用現成的 ASP.Net Long-Running Interval Task,並改進第一版的 SendMailTask 類別。
實驗過上一篇筆記的範例之後,已經知道那個範例在實務應用上仍有不足之處,其中一個比較重要的問題,就是背景工作無法得知 ASP.NET 應用程式是否正要結束,因而可能產生資料遺失的問題。
這篇筆記中的範例會使用 ASP.Net Long-Running Interval Task 裡面現成的 IntervalTask 類別來建立背景工作。
先說結論:這個 IntervalTask 類別只能建立一個 task,故也只適用於比較簡單的場合。如果需要在一個 ASP.NET 應用程式中建立多個定期執行的背景工作,就得自己動手修改此類別,或改用其他解決方案,如 FluentScheduler 或 Quartz.NET(寫在下一篇)。
實作練習
Step 1:下載 ASP.Net Long-Running Interval Task
下載完整原始碼之後,我們的 ASP.NET 應用程式只需要參考其中的 AspNetIntervalTask.csproj。
Step 2:修改 SendMailTask 類別
上一篇筆記中的 SendMailTask 類別含糊地包辦了「工作」和「排程」兩項任務(雖然稱不上排程,頂多就是定期執行而已,這裡只是為了方便說明)。現在有了現成的 IntervalTask 類別幫我們處理掉「排程」的動作,原先的 SendMailTask 類別就可以簡化許多。變成這樣:
IntervalTask 類別提供了偵測 ASP.NET 應用程式是否正要結束的功能,所以我們可以在背景工作中隨時檢查,若發現應用程式正要結束,就立刻結束背景工作。
Step 3:Global.asax
Step 4:部署至 IIS 並觀察執行結果
IIS 設定的部分請參考上一篇筆記。這次觀察的重點只有一個:網站啟動之後,查看 log.txt 檔案確認背景工作有執行,然後到 IIS 管理員中手動回收網站的 app pool。此時 log.txt 裡面應該會看到一則訊息:「Shutting down task at 某個時間」。
改善上一篇文章的 SendMailTask 類別
IntervalTask 之所以能收到 ASP.NET 應用程式即將關閉的通知,是因為實作了 System.Web.Hosting.IRegisteredObject 介面,並透過 HostingEnvironment.RegisterObject 方法向應用程式管理員登記:我(IntervalTask 物件本身)要收到應用程式結束通知。
System.Web.Hosting.IRegisteredObject 介面只定義了一個 Stop 方法:
void Stop(bool immediate)
其參數 immediate 代表外在執行環境是否要求立刻結束。ASP.NET 應用程式管理員第一次呼叫此方法時會傳入 false,讓我們的物件有一些緩衝時間來進行解除登記和收尾的工作。如果在一段時間(約 30 秒)之後,物件尚未完成解除登記的動作,應用程式管理員會再呼叫一次 Stop 方法,此時會傳入 true,對物件下最後通牒:就算你不解除登記,返回之後也會被自動移除。
了解這個註冊物件的機制之後,我們可以回頭把上一篇文章裡的 SendEmailTask 再加強一下,讓它也能夠在網站即將停止時收到通知。底下就是修改後的、依然陽春但包山包海自給自足的 SendEmailTask 背景工作類別,主要變動的部分是第 1 行、第 7 行、第 55~59 行:
小結
IntervalTask 類別只能建立一個 task,實用性打了折扣,但作為參考學習的範例還是不錯的。這次也一併修改了上一篇筆記中的 SendMailTask 類別,儘管陽春依舊,對於一些簡單的需求以及無法使用第三方元件的場合,還算堪用。
相關文章
實驗過上一篇筆記的範例之後,已經知道那個範例在實務應用上仍有不足之處,其中一個比較重要的問題,就是背景工作無法得知 ASP.NET 應用程式是否正要結束,因而可能產生資料遺失的問題。
這篇筆記中的範例會使用 ASP.Net Long-Running Interval Task 裡面現成的 IntervalTask 類別來建立背景工作。
先說結論:這個 IntervalTask 類別只能建立一個 task,故也只適用於比較簡單的場合。如果需要在一個 ASP.NET 應用程式中建立多個定期執行的背景工作,就得自己動手修改此類別,或改用其他解決方案,如 FluentScheduler 或 Quartz.NET(寫在下一篇)。
實作練習
Step 1:下載 ASP.Net Long-Running Interval Task
下載完整原始碼之後,我們的 ASP.NET 應用程式只需要參考其中的 AspNetIntervalTask.csproj。
Step 2:修改 SendMailTask 類別
上一篇筆記中的 SendMailTask 類別含糊地包辦了「工作」和「排程」兩項任務(雖然稱不上排程,頂多就是定期執行而已,這裡只是為了方便說明)。現在有了現成的 IntervalTask 類別幫我們處理掉「排程」的動作,原先的 SendMailTask 類別就可以簡化許多。變成這樣:
public class SendMailTask { public SendMailTask() { // 從組態檔載入相關參數,例如 SmtpHost、SmtpPort、SenderEmail 等等. } private void Log(string msg) { System.IO.File.AppendAllText(@"C:\Temp\log.txt", msg + Environment.NewLine); } public void DoSendMail() { Log("Entering DoSendMail() at " + DateTime.Now.ToString()); // 發送 email。這裡只固定輸出一筆文字訊息至 log 檔案,方便觀察測試。 // 每發送一封 email 就檢查一次 IntervalTask.Current.SuttingDown 以配合外部的終止事件。 while (Brass9.Threading.IntervalTask.Current.ShuttingDown == false) { string msg = String.Format("DoSendMail() at {0:yyyy/MM/dd HH:mm:ss}", DateTime.Now); Log(msg); Thread.Sleep(2000); } Log("Shutting down task at " + DateTime.Now.ToString()); } }
IntervalTask 類別提供了偵測 ASP.NET 應用程式是否正要結束的功能,所以我們可以在背景工作中隨時檢查,若發現應用程式正要結束,就立刻結束背景工作。
Step 3:Global.asax
public class Global : System.Web.HttpApplication { protected void Application_Start(object sender, EventArgs e) { var sendMailTask = new SendMailTask(); var task = IntervalTask.CreateTask(sendMailTask.DoSendMail); task.SetInterval(5000); // The following code throws exception because IntervalTask only support one task. /* var task2 = IntervalTask.CreateTask(() => { // dummy. }); task2.SetInterval(2000); */ } protected void Application_End(object sender, EventArgs e) { IntervalTask.Current.Dispose(); } }
Step 4:部署至 IIS 並觀察執行結果
IIS 設定的部分請參考上一篇筆記。這次觀察的重點只有一個:網站啟動之後,查看 log.txt 檔案確認背景工作有執行,然後到 IIS 管理員中手動回收網站的 app pool。此時 log.txt 裡面應該會看到一則訊息:「Shutting down task at 某個時間」。
改善上一篇文章的 SendMailTask 類別
IntervalTask 之所以能收到 ASP.NET 應用程式即將關閉的通知,是因為實作了 System.Web.Hosting.IRegisteredObject 介面,並透過 HostingEnvironment.RegisterObject 方法向應用程式管理員登記:我(IntervalTask 物件本身)要收到應用程式結束通知。
System.Web.Hosting.IRegisteredObject 介面只定義了一個 Stop 方法:
void Stop(bool immediate)
其參數 immediate 代表外在執行環境是否要求立刻結束。ASP.NET 應用程式管理員第一次呼叫此方法時會傳入 false,讓我們的物件有一些緩衝時間來進行解除登記和收尾的工作。如果在一段時間(約 30 秒)之後,物件尚未完成解除登記的動作,應用程式管理員會再呼叫一次 Stop 方法,此時會傳入 true,對物件下最後通牒:就算你不解除登記,返回之後也會被自動移除。
了解這個註冊物件的機制之後,我們可以回頭把上一篇文章裡的 SendEmailTask 再加強一下,讓它也能夠在網站即將停止時收到通知。底下就是修改後的、依然陽春但
public class SendMailTask : System.Web.Hosting.IRegisteredObject { private bool _stopping = false; public SendMailTask() { HostingEnvironment.RegisterObject(this); // 向 ASP.NET 應用程式管理員註冊此物件 } public void Run() { var aThread = new Thread(TaskLoop); aThread.IsBackground = true; aThread.Priority = ThreadPriority.BelowNormal; // 避免此背景工作拖慢 ASP.NET 處理 HTTP 請求. aThread.Start(); } private void Log(string msg) { System.IO.File.AppendAllText(@"C:\Temp\log.txt", msg + Environment.NewLine); } private void TaskLoop() { // 設定每一輪工作執行完畢之後要間隔幾分鐘再執行下一輪工作. const int LoopIntervalInMinutes = 1000 * 60 * 1; Log("TaskLoop on thread ID: " + Thread.CurrentThread.ManagedThreadId.ToString()); while (!_stopping) { try { DoSendMail(); } catch (Exception ex) { // 發生意外時只記在 log 裡,不拋出 exception,以確保迴圈持續執行. Log(ex.ToString()); } finally { // 每一輪工作完成後的延遲. System.Threading.Thread.Sleep(LoopIntervalInMinutes); } } } private void DoSendMail() { // 發送 email。這裡只固定輸出一筆文字訊息至 log 檔案,方便觀察測試。 string msg = String.Format("DoSendMail() at {0:yyyy/MM/dd HH:mm:ss}", DateTime.Now); Log(msg); } public void Stop(bool immediate) { _stopping = true; System.Web.Hosting.HostingEnvironment.UnregisterObject(this); } }
小結
IntervalTask 類別只能建立一個 task,實用性打了折扣,但作為參考學習的範例還是不錯的。這次也一併修改了上一篇筆記中的 SendMailTask 類別,儘管陽春依舊,對於一些簡單的需求以及無法使用第三方元件的場合,還算堪用。
相關文章
沒有留言: