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

来源:互联网 发布:microsoft fix it下载 编辑:程序博客网 时间:2024/03/29 14:24

1.缘起:

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

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

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

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

     

2.
适用场合:

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

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

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

 

3.设计思想与实现

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

    [EnumDescription("定时任务的类型")]
    
public enum TimingTaskType
    {
        [
EnumDescription("每小时一次")]
        PerHour,
        [
EnumDescription("每天一次")]
        PerDay,
        [
EnumDescription("每周一次")]
        PerWeek,
        [
EnumDescription("每月一次")]
        PerMonth
    }

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

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

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

            #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提供了RegisterTaskUnRegisterTask方法,用于在运行的过程中可以动态的增加或移除任务。

(4)TimingTaskManager必须对任务列表taskList进行加锁,以确保集合的线程安全。因为定时器本身就是在另外一个线程上执行Worker方法的,如果在执行Worker方法的同时,有其它线程调用RegisterTaskUnRegisterTask方法,就会导致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)由于ITimingTaskExcuterExcuteOnTime方法是在后台线程池中的某个线程上执行的,所以其抛出的任何异常都会被忽略。最好的办法是,在实现ExcuteOnTime方法是确保在其内部catch住了所有的异常。

 

5.扩展

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

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


原创粉丝点击