C#實(shí)現(xiàn)自定義定時組件的方法
本文實(shí)例展示的是一個自定義的定時器組件,有別于.NET Framework里面提供的幾個Timer。首先說說該組件開發(fā)背景,發(fā)現(xiàn)現(xiàn)在手頭上的工作離不開定時,定時做一個任務(wù),什么都是定時去做什么什么,隔某段時間干某件事情,都離不開“定時”這個詞。眾所周知這個要用到多線程,在多篇關(guān)于多線程的文章里面有提過做一些周期性的操作時最好用Timer,當(dāng)然這個Timer肯定是Threading.Timer,并不是WinForm控件里面的Timer。但我覺得在我的應(yīng)用中Timer不夠滿足需求。
1.Timer只能在做任務(wù)與任務(wù)之間相隔一段時間的操作,如下圖
但我需要的是這次任務(wù)開始的時刻到下次任務(wù)開始時刻是相隔同等的時間,又如下圖
這樣的情況下Timer則不能滿足需求。
2.Timer的時間間隔一般是定的,但是如果要每次執(zhí)行完任務(wù)要變動一下休眠的時間, 則需要調(diào)用Timer的Change方法。
3.Timer的休眠時間不是通過整形確定休眠的毫秒數(shù),就是用一個TimeSpan來確定,對于那種到每天多少多少時刻或者到每小時的多少分執(zhí)行一次的任務(wù)來說也不能夠完全方便使用
對于上面這三種,筆者對定時器封裝了一下,棄用了Timer,還是用回了原有的Thread,定義了一種描述重復(fù)時間的模式字符串,計(jì)算出TimeSpan,從而調(diào)用Thread的Sleep()方法來休眠。下面展示整個組件的類圖
最底下是兩個關(guān)于時間計(jì)算方面的類,兩個代理是兩種任務(wù)方法的委托,基類BaseCycleMission是周期任務(wù)的積累,實(shí)現(xiàn)了ICycle接口,主要進(jìn)行對任務(wù)線程的操控(開始,停止等),繼承他的兩個子類一個是實(shí)現(xiàn)上文第一點(diǎn)中我后來描述那種需求,一個類似于原有Timer的功能。它們各自使用不同的委托。MissionManager只是對所有周期任務(wù)的一個管理,統(tǒng)一去開啟或暫停某一類的任務(wù)。
時間計(jì)算模塊
那首先來介紹一下定義的字符串模式?,F(xiàn)在遇到的周期是有兩種模式,
一種是每隔多長時間要執(zhí)行一次的任務(wù),這個是最普通的周期形式,以每個5分鐘為例,完整的形式是“-99--99--99 -99:05:00”,“-99”是缺省的意思,當(dāng)然還有其他簡寫的模式;
另一種是沒到什么什么時候執(zhí)行一次任務(wù),例如沒到中午12點(diǎn)為例完整的形式是“ff-ff-ff 12: ff:ff”,“ff”是默認(rèn)的形式,當(dāng)然也可以用“FF”,這里也有其他簡寫的模式。
所有字符串的模式如下表所示
每到***時刻 | 每隔***時間 | |
完整 | ffff-ff-ff ff:ff:ff 或 ff-ff-ff ff:ff:ff |
-99--99--99 -99:-99:-99 |
日期部分 | ffff-ff-ff 或 ff-ff-ff |
-99--99--99 |
時間部分 | ff:ff:ff 或 ff:ff:ff |
-99:-99:-99 |
時間簡寫 | ff:ff 或 ff:ff |
-99:-99 |
那么時間計(jì)算模塊的處理流程是,給定了相應(yīng)的模式字符串,TimePointConverter借助正則表達(dá)式匹配出對應(yīng)的模式,返回匹配出來的年月日時分秒各個值,得出結(jié)果之后就調(diào)用SleepTimeProvider來計(jì)算出線程所要休眠的時間。下面則展示一下兩個類的部分代碼
public class TimePointConverter { //其他成員 private int[] DateTimeFixBuilder(string timeStr) { int[] result = null; string[] dtArray = timeStr.Split(); string[] dateArray = dtArray[0].Split('-'); string[] timeArray = dtArray[1].Split(':'); uint year,month,date; uint hour, minute, second; uint.TryParse(dateArray[0], out year); uint.TryParse(dateArray[1], out month); uint.TryParse(dateArray[2], out date); uint.TryParse(timeArray[0], out hour); uint.TryParse(timeArray[1], out minute); uint.TryParse(timeArray[2], out second); //return InnerFixBuilder(year, month, date, hour, minute, second); result = new int[] { (int)year, (int)month, (int)date, (int)hour, (int)minute, (int)second }; return result; } //其他成員 }
public class SleepTimeProvider { //其他成員 public TimeSpan InnerFixBuilder(uint year, uint month, uint date, uint hour, uint minute, uint second) { uint[] uintTimeArray = new uint[6] { year, month, date, hour, minute, second }; int[] intNowArray = new int[6] { DateTime.Now.Year,DateTime.Now.Month,DateTime.Now.Day, DateTime.Now.Hour,DateTime.Now.Minute,DateTime.Now.Second }; int[] intTimeArray = new int[6]; intTimeArray[0] = uintTimeArray[0] == 0 ? -DateTime.Now.Year : (int)uintTimeArray[0]; for (int i = 1; i < uintTimeArray.Length; i++) { intTimeArray[i] = intTimeArray[i - 1] < 0 && uintTimeArray[i] == 0 ? -intNowArray[i] : (int)uintTimeArray[i]; } DateTime goalTime = new DateTime(Math.Abs(intTimeArray[0]), Math.Abs(intTimeArray[1]), Math.Abs(intTimeArray[2]), Math.Abs(intTimeArray[3]), Math.Abs(intTimeArray[4]), Math.Abs(intTimeArray[5])); if (goalTime < DateTime.Now) { int max = -1; for (int i = intTimeArray.Length - 1; i >= 0; i--) { if (intTimeArray[i] < 0 && i > max) { max = i; intTimeArray[i]--; } intTimeArray[i] = Math.Abs(intTimeArray[i]); } goalTime = new DateTime(Math.Abs(intTimeArray[0]), Math.Abs(intTimeArray[1]), Math.Abs(intTimeArray[2]), Math.Abs(intTimeArray[3]), Math.Abs(intTimeArray[4]), Math.Abs(intTimeArray[5])); } return goalTime - DateTime.Now; } //其他成員 }
線程調(diào)用模塊
線程調(diào)用模塊是任務(wù)執(zhí)行的核心部分,MissionEntiy是對線程操作的封裝,主要負(fù)責(zé)開始,停止,暫停等操作。Thread用的是后臺線程,對線程操作時也多做幾個判斷。例如暫停那個操作的定義如下
public bool Pause() { if (actionThread == null) return false; if (actionThread.ThreadState == (System.Threading.ThreadState.Running | ThreadState.Background) || actionThread.ThreadState == (System.Threading.ThreadState.WaitSleepJoin | ThreadState.Background)) { actionThread.Suspend(); return true; } return false; }
CycleMission是真正的任務(wù)載體,里面都同樣有對線程的操作,但是又外加了一些時間處理,最核心的是讓線程的BuildMainAction方法,這個方法是計(jì)算出要休眠的時間,讓線程休眠,到點(diǎn)時調(diào)用適當(dāng)?shù)姆椒ㄎ小?/p>
public class BaseCycleMission:ICycleMission { //其他成員 protected void BuildMainAction(string normalCycle, string overTimeCycle, object overTimeDelegate, bool isSleepBefore,bool isInclude) { mainAction = () => { TimeSpan sleepTime=TimeSpan.MinValue; bool result = true; TimePointConvert.CircleType type ; #region 提前休眠 if (isSleepBefore) { type = TimePointConvert.Default.PraseType(normalCycle); if (type == TimePointConvert.CircleType.Interval) sleepTime = SleepTimeProvider.Defult.InnerIntervalBuilder( TimePointConvert.Default.ConvertCircle(normalCycle)); else sleepTime = SleepTimeProvider.Defult.InnerFixBuilder( TimePointConvert.Default.ConvertCircle(normalCycle)); if (sleepTime.TotalMilliseconds > 0) Thread.Sleep(sleepTime); } #endregion while (true) { #region 計(jì)算時間 if (isInclude) { if (result) { type = TimePointConvert.Default.PraseType(normalCycle); type = TimePointConvert.Default.PraseType(overTimeCycle); sleepTime = type == TimePointConvert.CircleType.Interval ? SleepTimeProvider.Defult.InnerIntervalBuilder( TimePointConvert.Default.ConvertCircle(overTimeCycle)) : SleepTimeProvider.Defult.InnerFixBuilder( TimePointConvert.Default.ConvertCircle(overTimeCycle)); } } #endregion #region 執(zhí)行方法 if(overTimeDelegate is OverTimeCycleDelegate) result = (overTimeDelegate as OverTimeCycleDelegate).Invoke(); else { (overTimeDelegate as CycleDelegate).Invoke(); result = true; } #endregion #region 計(jì)算時間 if (!isInclude) { if (result) { type = TimePointConvert.Default.PraseType(normalCycle); sleepTime = type == TimePointConvert.CircleType.Interval ? SleepTimeProvider.Defult.InnerIntervalBuilder( TimePointConvert.Default.ConvertCircle(normalCycle)) : SleepTimeProvider.Defult.InnerFixBuilder( TimePointConvert.Default.ConvertCircle(normalCycle)); } else { type = TimePointConvert.Default.PraseType(overTimeCycle); sleepTime = type == TimePointConvert.CircleType.Interval ? SleepTimeProvider.Defult.InnerIntervalBuilder( TimePointConvert.Default.ConvertCircle(overTimeCycle)) : SleepTimeProvider.Defult.InnerFixBuilder( TimePointConvert.Default.ConvertCircle(overTimeCycle)); } } #endregion if (sleepTime.TotalMilliseconds > 0) Thread.Sleep(sleepTime); } }; } //其他成員 }
當(dāng)然調(diào)用不是調(diào)用這個方法,調(diào)用只是調(diào)用它兩個幾類ExceptCycleMission和IncludeCycleMission,分別代表任務(wù)執(zhí)行的時間不包括在周期里面和包括在周期里面兩種。
管理器部分
管理器主要是一個字典集,是一個ICycleMission和字符串的字典集,里面包含了對集合里面所有元素的操作:增加,刪除,運(yùn)行,恢復(fù),暫停,停止。除了刪除和增加,其他都包含了類似下面的方法
RunAllMission() RunAllIncludeCycleMission() RunAllExceptCycleMission() RunMissionAmong(params string[] missionNames) RunMissionExcept(params string[] missionNames)
但是這堆方法里面都調(diào)用了CallAction這個方法,
private void CallAction(IEnumerable<ICycleMission> missionCollection,Action method) { if (missionCollection == null || method == null||missionCollection.Count()==0) return; foreach (ICycleMission item in missionCollection) { method.Method.Invoke(item, null); } }
例如在RunAllExceptCycleMission()方法里面調(diào)用如下
public void RunAllExceptCycleMission() { CallAction(this.Values.Where(c => c is ExceptCycleMission), BaseCycleMission.Default.RunMission); }
感興趣的朋友可以測試一下本文所述實(shí)例代碼,相信會有不小的收獲!
相關(guān)文章
C#利用FileSystemWatcher實(shí)時監(jiān)控文件的增加,修改,重命名和刪除
好多時候,我們都需要知道某些目錄下的文件什么時候被修改、刪除過等。本文將利用FileSystemWatcher實(shí)現(xiàn)實(shí)時監(jiān)控文件的增加,修改,重命名和刪除,感興趣的可以了解一下2022-08-08Unity 實(shí)現(xiàn)鼠標(biāo)滑過UI時觸發(fā)動畫的操作
這篇文章主要介紹了Unity 實(shí)現(xiàn)鼠標(biāo)滑過UI時觸發(fā)動畫的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-04-04詳解C#中Dictionary<TKey,TValue>的存儲結(jié)構(gòu)
無論是實(shí)際的項(xiàng)目中,還是在我們學(xué)習(xí)的過程中,都會重點(diǎn)的應(yīng)用到Dictionary<TKey,?TValue>這個存儲類型,所以本文就來為大家介紹一下這一存儲結(jié)構(gòu)的相關(guān)知識,希望對大家有所幫助2023-11-11通過實(shí)例解析c# yield關(guān)鍵字使用方法
這篇文章主要介紹了通過實(shí)例解析c# yield關(guān)鍵字使用方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-09-09