有些系統需要每隔一段時間就執行一下某個動作,比如,一個監控系統每隔 10 秒鐘就要檢測一下被監控對象的狀態是否正常,那這時我們就可以用到循環引擎了。
有人說可以使用 .NET 框架自帶定時器如 System.Threading.Timer ,嗯,沒錯。但是若這個類使用不當可能會引發后臺池線程耗盡的后果。因為 Timer 的定時事件觸發實在后臺線程池中的某個線程中處理的。也就是說 Timer 的每次定時事件觸發都會用到一個線程,如果定時的時間間隔小于事件處理的時間,則后臺線程池中將會有越來越多的線程被 Timer 使用掉,直至線程池中再無空閑的線程。
而
ESBasic.Threading.Engines.ICycleEngine
的設計目標是永遠都只使用一個線程。比如,它會隔
10
秒執行一個
Action
,執行完后再隔
10
秒再執行
Action
。間隔時間的等待與
Action
的執行都是在同一個線程中處理的。
循環引擎的形象示意圖如下:
根據上面的描述你應該已經看到了 ICycleEngine 與 Timer 之間的區別。由于 Action 的執行會占用額外的時間,所以 ICycleEngine 不適合于精確定時的任務。比如上面的例子,下一個 Action 開始的時刻與上一個 Action 開始的時刻的真正的時間差可能是 12 秒,而不是 10 秒,因為上一個 Action 的執行花費了 2 秒。
所以,如果你的系統不需要精確的定時任務,而且又不想花費過多的精力去防范使用
Timer
時線程耗盡的窘境出現,那么
ICycleEngine
將是個不錯的選擇。
3 .設計思想與實現
ICycleEngine 接口的源碼如下:
/// ICycleEngine在后臺線程中進行間隔循環的引擎
/// zhuweisky2006.12.21
/// </summary>
public interface ICycleEngine
{
/// <summary>
/// Start啟動后臺引擎線程
/// </summary>
void Start();
/// <summary>
/// Stop停止后臺引擎線程,只有當線程安全退出后,該方法才返回
/// </summary>
void Stop();
/// <summary>
/// IsRunning引擎是否運行中
/// </summary>
bool IsRunning{ get ;}
/// <summary>
/// DetectSpanInSecs引擎進行輪詢的間隔,DetectSpanInSecs=0,表示無間隙運作引擎;DetectSpanInSecs小于0則表示不使用引擎
/// </summary>
int DetectSpanInSecs{ get ; set ;}
/// <summary>
/// OnEngineStopped當引擎由運行變為停止狀態時,將觸發此事件。如果是異常停止,則事件參數為異常對象,否則,事件參數為null。
/// </summary>
event CbExceptionOnEngineStopped;
}
如何實現這個接口了?
由于不同的系統要求執行的 Action 不一樣,所以,我們可以實現一個 abstract 基類 BaseCycleEngine 來保證循環引擎的正常運轉,而派生類只要 override 基類的 abstract 方法 DoDetect 來執行自己的 Action 。
關于 BaseCycleEngine 的實現要注意以下幾點:
(1) 循環引擎是在后臺線程池的某個線程上運行的。
(2) 循環引擎可以無限次的啟動、停止、啟動、停止 ……
(3) 為了保證調用 Stop 方法時能迅速地停止引擎,我將間隔時間劃分為多個 BaseCycleEngine.SleepTime 。而不是一次性地 Sleep 間隔時間。
(4) 為了保證循環引擎真正停止后,才返回 Stop 方法的調用,我使用了 ManualResetEvent 來進行控制。
(5) DoDetect 方法的返回值為 false ,則表示在該 Action 執行完后將停止循環引擎。此后,可以重新調用 Start 方法再次啟動循環引擎。
4. 使用時的注意事項
(1) 要確保我們的 Action (即派生類的 DoDetect 方法)不任何拋出異常,否則會導致循環引擎異常停止,并導致循環引擎的內部狀態損壞而不可用。所以在派生類的 DoDetect 方法方法實現時捕捉所有的異常并加以處理。
(2) 在 DoDetect 方法實現中不能調用 Stop 方法,否則會導致死鎖出現。
(3) 如果將 DetectSpanInSecs 設為 0 ,則表示無間隙的執行 DoDetect 方法。而如果將 DetectSpanInSecs 設為負數,則表示不啟動循環引擎。
(4) 當引擎已經啟動并正在運行的過程中,如果要改變 DetectSpanInSecs 的值并使其生效,則必須重新啟動(先調用 Stop 方法再調用 Start 方法)引擎才可。
5. 擴展
( 1 ) AgileCycleEngine
在上面的介紹中,我們都是以 DoDetect 方法來表示要執行的 Action ,而且我們必須以繼承 BaseCycleEngine 的方式來使用循環引擎,這無疑限制了循環引擎的使用。
AgileCycleEngine
的存在便是為了突破這個限制。
{
private IEngineActorengineActor;
public AgileCycleEngine(IEngineActor_engineActor)
{
this .engineActor = _engineActor;
}
protected override bool DoDetect()
{
return this .engineActor.EngineAction();
}
}
AgileCycleEngine
繼承自
BaseCycleEngine
,但是它是非
abstract
的。
AgileCycleEngine
通過組合而非繼承的方式來使用循環引擎,我們可以將
Action
的執行者抽象為一個接口
IEngineActor
。
{
/// <summary>
/// EngineAction執行引擎動作,返回false表示停止引擎。
/// 注意,該方法不能拋出異常,否則會導致引擎停止運行(循環線程遭遇異常退出)。
/// </summary>
bool EngineAction();
}
通過實現 IEngineActor 來表明我們要執行的 Action ,然后將其注入到 AgileCycleEngine 中。
( 2 )永不停止的循環引擎
我們再考慮一個擴展的情況,假設我們的系統要求在啟動時就將引擎運行起來,而且在整個運行的生命周期中,都不需要停止引擎,那么我們可能不想將
Start
方法、
Stop
方法暴露出來以免意外的調用
Stop
方法而導致引擎停止運行,那這個時候我們可以使用類似下面的技巧來做到:
{
private AgileCycleEngine agileCycleEngine;
public void Initialize()
{
this .agileCycleEngine = new AgileCycleEngine( this );
this .agileCycleEngine.DetectSpanInSecs = 10 ;
this .agileCycleEngine.Start();
}
#region IEngineActor成員
public bool EngineAction()
{
//


return true ;
}
#endregion
}
用于示例的
MyCycleEngine
內部使用了
AgileCycleEngine
,但它沒有暴露循環引擎的任何控制方法,而且
Initialize
方法表明
MyCycleEngine
只要一初始化便開始運行,而且沒有辦法讓其停止運行。
MyCycleEngine
實現了
IEngineActor
接口,并把自己注入到
AgileCycleEngine
類型的成員中,于是引擎將每隔
10
秒鐘執行一次
MyCycleEngine
的
EngineAction
方法。
注:ESBasic源碼可到
http://esbasic.codeplex.com/
下載。
ESBasic討論:37677395
ESBasic開源前言
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

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