1.
緣起:
舉個例子也許就能夠說清楚回調定時器的用途。假設我的訂單系統接收各種不同類型的訂單,當訂單A進來時,系統根據訂單的類型和其它特徵進行綜合判斷後,決定A訂單要在2秒之後被方法M1處理;接下來收到的B訂單經過同樣的判斷後,決定要在10秒後被方法M2處理,……。這時候就可以用回調定時器來管理這些將要被延遲一定時間再執行的任務。
當然,我們可以使用定時器或前面介紹的迴圈引擎來實現這樣的功能,只不過我們自己需要手動管理註冊的定時回調任務,並且定時檢查每一個未處理訂單是否已經到了處理的時刻。而回調定時器已經自動幫我們做好了這些事情,而且還是一個與具體應用無關的萬用群組件,我們不需要再重複實現一個特定的類來做這件事情。
回調定時器的形象如下:
2.適用場合:
設計回調定時器ESBasic.Threading.Timers.ICallbackTimer的主要是為瞭解決類似下面的問題:
(1) 任務(回調)需要被延遲某一個時間間隔後執行。
(2)不同的任務需要被延遲的時間間隔可能不同。
(3)不同的任務需要被處理的方式可能不同。
(4)回調的延遲不需要非常精確。
回調定時器要解決的最主要問題是第一點,後面兩點也是回調定時器支援的重要特性。
3.設計思想與實現
ICallbackTimer介面的定義如下:
/// <summary>
/// ICallbackTimer 回調定時器。
/// 注意:回調任務會非同步在ThreadPool的WorkerThread上執行。即使目標任務拋出異常也不會影響INotifyTimer的繼續運行。
/// </summary>
public interface ICallbackTimer<T> : ICycleEngine
{
int TaskCount { get; }
/// <summary>
/// AddCallback 添加一個回調任務。目標任務會在spanInSecs後運行。僅僅運行一次。
/// </summary>
/// <param name="spanInSecs">多少秒後執行任務</param>
/// <param name="_callback">目標方法的委託</param>
/// <param name="_callbackPara">調用目標方法的參數</param>
/// <returns>新的任務編號</returns>
int AddCallback(int spanInSecs, CbGeneric<T> _callback, T _callbackPara);
/// <summary>
/// RemoveCallback 刪除目標回調任務。
/// </summary>
void RemoveCallback(int taskID);
/// <summary>
/// RemoveCallbackAndAddNew 刪除目標回調任務,並添加一個新的回調任務。
/// </summary>
int RemoveCallbackAndAddNew(int taskIDToRemoved, int spanInSecs, CbGeneric<T> _newCallback, T _newCallbackPara);
/// <summary>
/// GetLeftSeconds 離目標任務被回調執行還有多長時間(s)。返回0,表示任務不存在或者任務已經被執行。
/// </summary>
int GetLeftSeconds(int taskID);
/// <summary>
/// Clear 清除所有回調任務。
/// </summary>
void Clear();
}
根據上述對回調定時器的描述,我們可以藉助迴圈引擎來實現它。你已經看到,ICallbackTimer繼承了ICycleEngine介面,這說明我們可以通過Start、Stop方法來控制回調定時器的運行,並通過DetectSpanInSecs屬性來設定檢測任務狀態的時間間隔,當然,DetectSpanInSecs設定的值最好小於最小的回調任務的延遲時間間隔。
ICallbackTimer介面的泛型參數T,代表的是回調執行時所用到的參數的類型。而回調方法的簽名必須是只接受一個T類型的參數,並且沒有傳回值(即如泛型委派CbGeneric<T>)。
AddCallback方法返回一個int,表示添加的任務的唯一編號,我們可以通過這個編號來查詢該任務離被回調執行的時間(GetLeftSeconds方法),或者根據該編號來取消目標回調任務的執行(RemoveCallback方法)。
CallbackTimer實現了ICallbackTimer介面,其實現要注意以下幾點:
(1)CallbackTimer繼承自BaseCycleEngine,它藉助於迴圈引擎來進行任務狀態的迴圈檢測。
(2)為了允許在多線程的環境中回調定時器,CallbackTimer必須對內部集合(dicTask)進行加鎖控制。
(3)CallbackTimer使用CallbackTask類來將一個定時回調任務封裝起來。
(4)回調任務是非同步在背景ThreadPool的WorkerThread上執行的。所以達到執行條件的多個回調任務不會相互阻塞,而是幾乎同時執行的。
(5)在DoDetect方法的實現中,我們先拷貝一份工作清單,然後再對其作foreach,而不是直接this.dicTask.Values作foreach,這是因為,如果在某個回調執行時,調用了AddCallback/RemoveCallback將修改this.dicTask的內容,而此時對this.dicTask.Values的foreach還未結束,這時foreach將拋出異常。
4. 使用時的注意事項
首先,是要注意GetLeftSeconds返回的值是不精確的。因為CallbackTimer是在每次迴圈檢測的時候(覆寫基類的DoDetect方法),修改每個CallbackTask的LeftSeconds的(通過CallbackTask的SecondsPassed方法)。所以,GetLeftSeconds方法返回的只是一個大概的而非精確的值。
其次,回調的執行是“一次性的”,即註冊的一個回調任務只會被執行一次,或者被取消。
再次,回調任務定時器允許回調任務執行時拋出異常,只不過該異常會被回調定時器忽略。所以,如果你要處理該異常,就應該在回調方法中捕獲這個異常。
最後,由於回調任務是非同步在後台線程池中啟動並執行,所以如果同時被執行的回調的任務很多,其數量超過了後台線程池中的線程數量,此時就會導致某些回調任務將會被進一步順延強制。
5.擴充
回調定時器ICallbackTimer暫時沒有任何擴充。
註:ESBasic源碼可到http://esbasic.codeplex.com/下載。