.NET框架中的可復(fù)用類庫ESBasic
1.緣起:
有些系統(tǒng)需要每隔一段時(shí)間就執(zhí)行一下某個(gè)動(dòng)作,比如,一個(gè)監(jiān)控系統(tǒng)每隔10秒鐘就要檢測(cè)一下被監(jiān)控對(duì)象的狀態(tài)是否正常,那這時(shí)我們就可以用到循環(huán)引擎了。
有人說可以使用.NET框架自帶定時(shí)器如System.Threading.Timer,嗯,沒錯(cuò)。但是若這個(gè)類使用不當(dāng)可能會(huì)引發(fā)后臺(tái)池線程耗盡的后果。因?yàn)門imer的定時(shí)事件觸發(fā)實(shí)在后臺(tái)線程池中的某個(gè)線程中處理的。也就是說Timer的每次定時(shí)事件觸發(fā)都會(huì)用到一個(gè)線程,如果定時(shí)的時(shí)間間隔小于事件處理的時(shí)間,則后臺(tái)線程池中將會(huì)有越來越多的線程被Timer使用掉,直至線程池中再無空閑的線程。
而ESBasic.Threading.Engines.ICycleEngine的設(shè)計(jì)目標(biāo)是永遠(yuǎn)都只使用一個(gè)線程。比如,它會(huì)隔10秒執(zhí)行一個(gè)Action,執(zhí)行完后再隔10秒再執(zhí)行Action。間隔時(shí)間的等待與Action的執(zhí)行都是在同一個(gè)線程中處理的。
2.適用場(chǎng)合:
根據(jù)上面的描述你應(yīng)該已經(jīng)看到了ICycleEngine與Timer之間的區(qū)別。由于Action的執(zhí)行會(huì)占用額外的時(shí)間,所以ICycleEngine不適合于精確定時(shí)的任務(wù)。比如上面的例子,下一個(gè)Action開始的時(shí)刻與上一個(gè)Action開始的時(shí)刻的真正的時(shí)間差可能是12秒,而不是10秒,因?yàn)樯弦粋€(gè)Action的執(zhí)行花費(fèi)了2秒。
所以,如果你的系統(tǒng)不需要精確的定時(shí)任務(wù),而且又不想花費(fèi)過多的精力去防范使用Timer時(shí)線程耗盡的窘境出現(xiàn),那么ICycleEngine將是個(gè)不錯(cuò)的選擇。
3.設(shè)計(jì)思想與實(shí)現(xiàn)
ICycleEngine接口的源碼如下:
- /// <summary>
- /// ICycleEngine 在后臺(tái)線程中進(jìn)行間隔循環(huán)的引擎
- /// zhuweisky 2006.12.21
- /// </summary>
- public interface ICycleEngine
- {
- /// <summary>
- /// Start 啟動(dòng)后臺(tái)引擎線程
- /// </summary>
- void Start();
- /// <summary>
- /// Stop 停止后臺(tái)引擎線程,只有當(dāng)線程安全退出后,該方法才返回
- /// </summary>
- void Stop();
- /// <summary>
- /// IsRunning 引擎是否運(yùn)行中
- /// </summary>
- bool IsRunning { get; }
- /// <summary>
- /// DetectSpanInSecs 引擎進(jìn)行輪詢的間隔,DetectSpanInSecs=0,表示無間隙運(yùn)作引擎;DetectSpanInSecs小于0則表示不使用引擎
- /// </summary>
- int DetectSpanInSecs { get;set; }
- /// <summary>
- /// OnEngineStopped 當(dāng)引擎由運(yùn)行變?yōu)橥V範(fàn)顟B(tài)時(shí),將觸發(fā)此事件。如果是異常停止,則事件參數(shù)為異常對(duì)象,否則,事件參數(shù)為null。
- /// </summary>
- event CbException OnEngineStopped;
- }
如何實(shí)現(xiàn)這個(gè)接口了?
由于不同的系統(tǒng)要求執(zhí)行的Action不一樣,所以,我們可以實(shí)現(xiàn)一個(gè)abstract基類BaseCycleEngine來保證循環(huán)引擎的正常運(yùn)轉(zhuǎn),而派生類只要override基類的abstract方法DoDetect來執(zhí)行自己的Action。
關(guān)于BaseCycleEngine的實(shí)現(xiàn)要注意以下幾點(diǎn):
(1)循環(huán)引擎是在后臺(tái)線程池的某個(gè)線程上運(yùn)行的。
(2)循環(huán)引擎可以無限次的啟動(dòng)、停止、啟動(dòng)、停止……
(3)為了保證調(diào)用Stop方法時(shí)能迅速地停止引擎,我將間隔時(shí)間劃分為多個(gè)BaseCycleEngine.SleepTime。而不是一次性地Sleep間隔時(shí)間。
(4)為了保證循環(huán)引擎真正停止后,才返回Stop方法的調(diào)用,我使用了ManualResetEvent來進(jìn)行控制。
(5)DoDetect方法的返回值為false,則表示在該Action執(zhí)行完后將停止循環(huán)引擎。此后,可以重新調(diào)用Start方法再次啟動(dòng)循環(huán)引擎。
4. 使用時(shí)的注意事項(xiàng)
(1) 要確保我們的Action(即派生類的DoDetect方法)不任何拋出異常,否則會(huì)導(dǎo)致循環(huán)引擎異常停止,并導(dǎo)致循環(huán)引擎的內(nèi)部狀態(tài)損壞而不可用。所以在派生類的DoDetect方法方法實(shí)現(xiàn)時(shí)捕捉所有的異常并加以處理。
(2) 在DoDetect方法實(shí)現(xiàn)中不能調(diào)用Stop方法,否則會(huì)導(dǎo)致死鎖出現(xiàn)。
(3) 如果將DetectSpanInSecs設(shè)為0,則表示無間隙的執(zhí)行DoDetect方法。而如果將DetectSpanInSecs設(shè)為負(fù)數(shù),則表示不啟動(dòng)循環(huán)引擎。
(4) 當(dāng)引擎已經(jīng)啟動(dòng)并正在運(yùn)行的過程中,如果要改變DetectSpanInSecs的值并使其生效,則必須重新啟動(dòng)(先調(diào)用Stop方法再調(diào)用Start方法)引擎才可。
5.擴(kuò)展
(1)AgileCycleEngine
在上面的介紹中,我們都是以DoDetect方法來表示要執(zhí)行的Action,而且我們必須以繼承BaseCycleEngine的方式來使用循環(huán)引擎,這無疑限制了循環(huán)引擎的使用。
AgileCycleEngine的存在便是為了突破這個(gè)限制。
- public sealed class AgileCycleEngine :BaseCycleEngine
- {
- private IEngineActor engineActor;
- public AgileCycleEngine(IEngineActor _engineActor)
- {
- this.engineActor = _engineActor;
- }
- protected override bool DoDetect()
- {
- return this.engineActor.EngineAction();
- }
- }
AgileCycleEngine繼承自BaseCycleEngine,但是它是非abstract的。AgileCycleEngine通過組合而非繼承的方式來使用循環(huán)引擎,我們可以將Action的執(zhí)行者抽象為一個(gè)接口IEngineActor。
- public interface IEngineActor
- {
- /// <summary>
- /// EngineAction 執(zhí)行引擎動(dòng)作,返回false表示停止引擎。
- /// 注意,該方法不能拋出異常,否則會(huì)導(dǎo)致引擎停止運(yùn)行(循環(huán)線程遭遇異常退出)。
- /// </summary>
- bool EngineAction() ;
- }
通過實(shí)現(xiàn)IEngineActor來表明我們要執(zhí)行的Action,然后將其注入到AgileCycleEngine中。
(2)永不停止的循環(huán)引擎
我們?cè)倏紤]一個(gè)擴(kuò)展的情況,假設(shè)我們的系統(tǒng)要求在啟動(dòng)時(shí)就將引擎運(yùn)行起來,而且在整個(gè)運(yùn)行的生命周期中,都不需要停止引擎,那么我們可能不想將Start方法、Stop方法暴露出來以免意外的調(diào)用Stop方法而導(dǎo)致引擎停止運(yùn)行,那這個(gè)時(shí)候我們可以使用類似下面的技巧來做到:
- public sealed class MyCircleEngine : IEngineActor
- {
- private AgileCycleEngine agileCycleEngine;
- public void Initialize()
- {
- this.agileCycleEngine = new AgileCycleEngine(this);
- this.agileCycleEngine.DetectSpanInSecs = 10;
- this.agileCycleEngine.Start();
- }
- #region IEngineActor 成員
- public bool EngineAction()
- {
- // My Action
- return true;
- }
- #endregion
- }
用于示例的MyCycleEngine內(nèi)部使用了AgileCycleEngine,但它沒有暴露循環(huán)引擎的任何控制方法,而且Initialize方法表明MyCycleEngine只要一初始化便開始運(yùn)行,而且沒有辦法讓其停止運(yùn)行。MyCycleEngine實(shí)現(xiàn)了IEngineActor接口,并把自己注入到AgileCycleEngine類型的成員中,于是引擎將每隔10秒鐘執(zhí)行一次MyCycleEngine的EngineAction方法。
【編輯推薦】