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

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