1. 緣起:
假設我們的報表系統需要在每天的 00:05:00 統計前一天的報表數據,需要在每周一的 00:30:00 統計上周的報表數據,又需要在每月 1 日的 00:30:00 統計上月的報表數據。
這些報表統計任務是很常見的系統需求,對于類似這樣的在指定時刻執行的定時任務,我使用 ESBasic.Threading.Timers.TimingTaskManager (定時任務管理器)來處理它。
TimingTaskManager 與前面講的回調定時器 CallbackTimer 的區別在于, CallbackTimer 是參考當前時間再延遲一段時間后執行,而 TimingTaskManager 管理的任務是要求在指定的具體時間點執行。
定時任務管理器的形象示意圖如下:
如果你的任務滿足以下條件,則可以使用 TimingTaskManager 來解決任務的定時執行:
(1) 任務需要在每小時、每天、每周、或每月的某個固定的時間點執行。
(2) 可以允許任務執行的時間點與期望的時刻存在一定的誤差。
3 .設計思想與實現
在介紹
TimingTaskManager
之前,我們要先介紹
TimingTask
這個類,它表示一個定時任務,正是它封裝了任務的執行頻率、執行的具體時間和要執行的目標方法。
TimingTask
的類圖如下:
我們看到,
ExcuteTime
屬性是一個
ShortTime
類型,指定要執行任務的具體時刻。而
TimingTaskType
屬性決定了
TimingTask
執行的頻率,
TimingTaskType
定義如下:
public enum TimingTaskType
{
[ EnumDescription( " 每小時一次 " )]
PerHour,
[ EnumDescription( " 每天一次 " )]
PerDay,
[ EnumDescription( " 每周一次 " )]
PerWeek,
[ EnumDescription( " 每月一次 " )]
PerMonth
}
要注意的是,如果 TimingTaskType 屬性的值為 PerHour ,則將忽略 ExcuteTime 的 Hour 屬性。
同樣的, DayOfWeek 屬性只有在 TimingTaskType 屬性的值為 PerWeek 時才有效,表示在周幾執行。 Day 屬性只有在 TimingTaskType 屬性的值為 PerMonth 時才有效,表示在每月的幾號執行。
在 TimingTask 的實現中, IsOnTime 方法的實現特別要引起注意。因為我們的定時任務管理器是基于定時器 Timer 工作的,而定時器的掃描時間是有間隔的,所以,在某個 ExcuteTime 所代表的真正的執行時間點的左右的兩個掃描時刻,可能都會被認為是符合執行條件的(比如,兩個掃描時刻距離真正執行時刻的距離都在 1 秒之內),如果是這樣,目標任務將會被執行兩次――這是我們不希望發生的。為了避免這種情況的出現,我們在 TimingTask 中使用 lastRightTime 成員來記錄上次執行的時間,如果 lastRightTime 與當前時間的差值 2 倍的掃描間隔以內,則將認為當前時間不符合條件。正如下面代碼所示:
TimeSpan span = now - this .lastRightTime;
if (span.TotalMilliseconds < checkSpanSeconds * 1000 * 2 )
{
return false ;
}
#endregion
接下來,我們將注意力轉移到
TimingTaskManager
上來。有了
TimingTask
的封裝,
TimingTaskManager
所要做的事情就非常簡單,其要點歸結如下:
(1) TimingTaskManager 使用 Timer 來進行定時掃描,以判斷每個任務是否到了要執行的時間點。 TimerSpanInSecs 屬性指定了掃描的時間間隔。
(2) 當某個任務的執行時刻到來, TimingTaskManager 會異步執行該任務,這樣不會阻塞當前的 foreach 遍歷。
(3) TimingTaskManager 提供了 RegisterTask 和 UnRegisterTask 方法,用于在運行的過程中可以動態的增加或移除任務。
(4) TimingTaskManager 必須對任務列表 taskList 進行加鎖,以確保集合的線程安全。因為定時器本身就是在另外一個線程上執行 Worker 方法的,如果在執行 Worker 方法的同時,有其它線程調用 RegisterTask 和 UnRegisterTask 方法,就會導致 Worker 方法中的 foreach 遍歷動作拋出異常。
4. 使用時的注意事項
(1) 由于 TimingTaskManager 采用 Timer 進行定時掃描,所以,任務執行的時間點與期望的時間點的最大誤差就是 TimerSpanInSecs 的值。由于 TimerSpanInSecs 能取的最小值為 1 秒,所以 TimingTaskManager 能夠達到的最小誤差為 1 秒。如果你的任務期望被更精確的執行,那么 TimingTaskManager 就不適合你。
(2) TimingTaskType 指定的頻率只能是:每小時一次、每天一次、每周一次、每月一次。但是對于一個類似你希望在每周二、四中午 12:00:00 執行的任務,我們可以采用變通的做法,那就是將其視為兩個任務:一個在每周二的中午 12:00:00 執行,另一個在每周四的中午 12:00:00 執行。如此,我們可以使用 TimingTaskManager 提供的最基礎的定時頻率經過組合來處理更高級、更復雜的定時任務。
(3) 由于 ITimingTaskExcuter 的 ExcuteOnTime 方法是在后臺線程池中的某個線程上執行的,所以其拋出的任何異常都會被忽略。最好的辦法是,在實現 ExcuteOnTime 方法是確保在其內部 catch 住了所有的異常。
5. 擴展
定時任務管理器
TimingTaskManager
暫時沒有任何擴展。
注:ESBasic源碼可到
http://esbasic.codeplex.com/
下載。
ESBasic討論:37677395
ESBasic開源前言
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
