举个例子也许就能够说清楚回调定时器的用途。假设我的订单系统接收各种不同类型的订单,当订单 A 进来时,系统根据订单的类型和其它特征进行综合判断后,决定 A 订单要在 2 秒之后被方法 M1 处理;接下来收到的 B 订单经过同样的判断后,决定要在 10 秒后被方法 M2 处理, …… 。这时候就可以用回调定时器来管理这些将要被延迟一定时间再执行的任务。
当然,我们可以使用定时器或前面介绍的循环引擎来实现这样的功能,只不过我们自己需要手动管理注册的定时回调任务,并且定时检查每一个未处理订单是否已经到了处理的时刻。而回调定时器已经自动帮我们做好了这些事情,而且还是一个与具体应用无关的通用组件,我们不需要再重复实现一个特定的类来做这件事情。
回调定时器的形象示意图如下:
设计回调定时器 ESBasic.Threading.Timers.ICallbackTimer 的主要是为了解决类似下面的问题:
(1) 任务(回调)需要被延迟某一个时间间隔后执行。
(2) 不同的任务需要被延迟的时间间隔可能不同。
(3) 不同的任务需要被处理的方式可能不同。
(4) 回调的延迟不需要非常精确。
回调定时器要解决的最主要问题是第一点,后面两点也是回调定时器支持的重要特性。
3 .设计思想与实现
ICallbackTimer 接口的定义如下:
/// ICallbackTimer回调定时器。
/// 注意:回调任务会异步在ThreadPool的WorkerThread上执行。即使目标任务抛出异常也不会影响INotifyTimer的继续运行。
/// </summary>
public interface ICallbackTimer < T > : ICycleEngine
{
int TaskCount{ get ;}
/// <summary>
/// AddCallback添加一个回调任务。目标任务会在spanInSecs后运行。仅仅运行一次。
/// </summary>
/// <paramname="spanInSecs"> 多少秒后执行任务 </param>
/// <paramname="_callback"> 目标方法的委托 </param>
/// <paramname="_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/
下载。