ESBasic 可复用的.NET类库(08) -- 定时任务

系统 1430 0

1. 缘起:

假设我们的报表系统需要在每天的 00:05:00 统计前一天的报表数据,需要在每周一的 00:30:00 统计上周的报表数据,又需要在每月 1 日的 00:30:00 统计上月的报表数据。

这些报表统计任务是很常见的系统需求,对于类似这样的在指定时刻执行的定时任务,我使用 ESBasic.Threading.Timers.TimingTaskManager (定时任务管理器)来处理它。

TimingTaskManager 与前面讲的回调定时器 CallbackTimer 的区别在于, CallbackTimer 是参考当前时间再延迟一段时间后执行,而 TimingTaskManager 管理的任务是要求在指定的具体时间点执行。

定时任务管理器的形象示意图如下:

ESBasic 可复用的.NET类库(08) -- 定时任务管理器 TimingTaskManager

2.
适用场合:

如果你的任务满足以下条件,则可以使用 TimingTaskManager 来解决任务的定时执行:

(1) 任务需要在每小时、每天、每周、或每月的某个固定的时间点执行。

(2) 可以允许任务执行的时间点与期望的时刻存在一定的误差。

3 .设计思想与实现

在介绍 TimingTaskManager 之前,我们要先介绍 TimingTask 这个类,它表示一个定时任务,正是它封装了任务的执行频率、执行的具体时间和要执行的目标方法。 TimingTask 的类图如下:
ESBasic 可复用的.NET类库(08) -- 定时任务管理器 TimingTaskManager
我们看到,
ExcuteTime 属性是一个 ShortTime 类型,指定要执行任务的具体时刻。而 TimingTaskType 属性决定了 TimingTask 执行的频率, TimingTaskType 定义如下:

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> [ EnumDescription( " 定时任务的类型 " )]
public enum TimingTaskType
{
[
EnumDescription( " 每小时一次 " )]
PerHour,
[
EnumDescription( " 每天一次 " )]
PerDay,
[
EnumDescription( " 每周一次 " )]
PerWeek,
[
EnumDescription( " 每月一次 " )]
PerMonth
}

要注意的是,如果 TimingTaskType 属性的值为 PerHour ,则将忽略 ExcuteTime Hour 属性。

同样的, DayOfWeek 属性只有在 TimingTaskType 属性的值为 PerWeek 时才有效,表示在周几执行。 Day 属性只有在 TimingTaskType 属性的值为 PerMonth 时才有效,表示在每月的几号执行。

TimingTask 的实现中, IsOnTime 方法的实现特别要引起注意。因为我们的定时任务管理器是基于定时器 Timer 工作的,而定时器的扫描时间是有间隔的,所以,在某个 ExcuteTime 所代表的真正的执行时间点的左右的两个扫描时刻,可能都会被认为是符合执行条件的(比如,两个扫描时刻距离真正执行时刻的距离都在 1 秒之内),如果是这样,目标任务将会被执行两次――这是我们不希望发生的。为了避免这种情况的出现,我们在 TimingTask 中使用 lastRightTime 成员来记录上次执行的时间,如果 lastRightTime 与当前时间的差值 2 倍的扫描间隔以内,则将认为当前时间不符合条件。正如下面代码所示:

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> #region 防止在临界点时,执行两次
TimeSpan span = now - this .lastRightTime;
if (span.TotalMilliseconds < checkSpanSeconds * 1000 * 2 )
{
return false ;
}
#endregion


接下来,我们将注意力转移到 TimingTaskManager 上来。有了 TimingTask 的封装, TimingTaskManager 所要做的事情就非常简单,其要点归结如下:

(1) TimingTaskManager 使用 Timer 来进行定时扫描,以判断每个任务是否到了要执行的时间点。 TimerSpanInSecs 属性指定了扫描的时间间隔。

(2) 当某个任务的执行时刻到来, TimingTaskManager 会异步执行该任务,这样不会阻塞当前的 foreach 遍历。

(3) TimingTaskManager 提供了 RegisterTask UnRegisterTask 方法,用于在运行的过程中可以动态的增加或移除任务。

(4) TimingTaskManager 必须对任务列表 taskList 进行加锁,以确保集合的线程安全。因为定时器本身就是在另外一个线程上执行 Worker 方法的,如果在执行 Worker 方法的同时,有其它线程调用 RegisterTask UnRegisterTask 方法,就会导致 Worker 方法中的 foreach 遍历动作抛出异常。

4. 使用时的注意事项

(1) 由于 TimingTaskManager 采用 Timer 进行定时扫描,所以,任务执行的时间点与期望的时间点的最大误差就是 TimerSpanInSecs 的值。由于 TimerSpanInSecs 能取的最小值为 1 秒,所以 TimingTaskManager 能够达到的最小误差为 1 秒。如果你的任务期望被更精确的执行,那么 TimingTaskManager 就不适合你。

(2) TimingTaskType 指定的频率只能是:每小时一次、每天一次、每周一次、每月一次。但是对于一个类似你希望在每周二、四中午 12:00:00 执行的任务,我们可以采用变通的做法,那就是将其视为两个任务:一个在每周二的中午 12:00:00 执行,另一个在每周四的中午 12:00:00 执行。如此,我们可以使用 TimingTaskManager 提供的最基础的定时频率经过组合来处理更高级、更复杂的定时任务。

(3) 由于 ITimingTaskExcuter ExcuteOnTime 方法是在后台线程池中的某个线程上执行的,所以其抛出的任何异常都会被忽略。最好的办法是,在实现 ExcuteOnTime 方法是确保在其内部 catch 住了所有的异常。

5. 扩展

定时任务管理器 TimingTaskManager 暂时没有任何扩展。

注:ESBasic源码可到 http://esbasic.codeplex.com/ 下载。
ESBasic讨论:37677395
ESBasic开源前言


ESBasic 可复用的.NET类库(08) -- 定时任务管理器 TimingTaskManager


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论