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


    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已经开源,点击这里下载源码。
        
    ESBasic开源前言


  • 相关阅读:
    Java AJAX开发系列 5,ZK参考资料
    现代浏览器客户端Web开发 Project Silk
    Java AJAX开发系列 2,项目中使用ZK
    Java性能分析点滴
    Java AJAX开发系列 4,ZK应用实例
    Java AJAX开发系列 3, ZK MVC
    大型网站如何架构 网页资料集
    Google Analytics 进行网站流量分析
    ALM TFS/VSTS工具 的Java集成
    系统各层关注的内容【DDDD笔记】
  • 原文地址:https://www.cnblogs.com/zhuweisky/p/1574338.html
Copyright © 2020-2023  润新知