摘要:續上集,使用現成的 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 類別,儘管陽春依舊,對於一些簡單的需求以及無法使用第三方元件的場合,還算堪用。
相關文章
沒有留言: