QWorker 使用 QWorker 做为计划任务引擎

Discussion in 'QDAC' started by swish, Aug 12, 2015.

  1. swish

    swish Staff Member Moderater

    Apr 13, 2015
    QWorker 提供了 Plan 函数来提供计划任务功能的支持。每个任务做为一个作业,可以在指定的时间点被触发执行。而 cron 作为 Linux 操作系统下计划任务的标准被广大用户所熟知,QWorker 也就不再闭门造车,决定遵从这一规则。但是,可但是,通过百度后,你会发现有两个,一个是 crontab 标准,精确到分钟,另一个是 java 的 spring 框架使用的仿 cron 格式,精确到秒。在一翻无谓的挣扎和犹豫之后,最终决定使用 spring 兼容的实现,原来基于 cron 的实现被推倒重新实现。

    好了,确定了目标并实现之后,我们就是实践了。计划任务作业的创建与普通的作业其实并没有什么不同,如果非要说有啥不同,那就是计划任务作业的接口函数名为 Plan,并且使用了一个 TQPlanMask 类型的参数来传递计划任务的调度计划。

    先来看看 Plan 函数的声明:
    Code (Text):
    1. functionPlan(AProc:TQJobProc;constAPlan:TQPlanMask;AData:Pointer;ARunInMainThread:Boolean=False;AFreeType:TQJobDataFreeType=jdfFreeByUser):IntPtr;overload;
    2. functionPlan(AProc:TQJobProc;constAPlan:QStringW;AData:Pointer;ARunInMainThread:Boolean=False;AFreeType:TQJobDataFreeType=jdfFreeByUser):IntPtr;overload;
    3. functionPlan(AProc:TQJobProcG;constAPlan:TQPlanMask;AData:Pointer;ARunInMainThread:Boolean=False;AFreeType:TQJobDataFreeType=jdfFreeByUser):IntPtr;overload;
    4. functionPlan(AProc:TQJobProcG;constAPlan:QStringW;AData:Pointer;ARunInMainThread:Boolean=False;AFreeType:TQJobDataFreeType=jdfFreeByUser):IntPtr;overload;
    5. {$IFDEF UNICODE}
    6. functionPlan(AProc:TQJobProcA;constAPlan:TQPlanMask;AData:Pointer;ARunInMainThread:Boolean=False;AFreeType:TQJobDataFreeType=jdfFreeByUser):IntPtr;overload;
    7. functionPlan(AProc:TQJobProcA;constAPlan:QStringW;AData:Pointer;ARunInMainThread:Boolean=False;AFreeType:TQJobDataFreeType=jdfFreeByUser):IntPtr;overload;
    8. {$ENDIF}
    好了,简单说下几个参数都干啥的,AProc、AData、ARunInMainThread、AFreeType 这四个参数与普通的作业并没有任何不同,依然是作业处理函数、附加数据指针、是否运行在主线程、附加数据指针的释放方式,唯一多出来的参数就是 APlan,从上面的声明可以看出来,我们提供了两个重载,一个是 TQPlanMask 类型,另外一个是直接的 QStringW,我们可以根据需要选择一个格式:

    • TQPlanMask 格式可以提供更多额外的控制,比如计划任务的首次执行时间范围。
    • 如果没有额外的控制需要,QStringW 格式是直接设置作业调度的掩码,明显更方便一些。
    下面我们看调用的一个例子:
    Code (Text):
    1. Workers.Plan(DoPlanJob,'0 * * * * * "每分钟重复一次的作业"',nil,True);
    这样一句代码就定义了一个计划任务,要求在每分钟执行一次作业 DoPlanJob。为什么呢,我们就要从这个计划任务的格式掩码说起。

    QWorker 的计划任务掩码参考自 Spring ,共有 6-7 部分组成,格式如下:

    秒 分 时 日 月 周 [年]

    其中,年是可选,没有的话,就忽略它的检查。第一个部分都有自己的取值范围和为了方便条件设定的通配符,年、月、日和时、分、秒我觉得就不用细说取值范围了,相信你也知道,只是特别说明以下几点:
    • 小时使用的是 24 小时制,所以范围是0-23;
    • 月可以使用月份的英文缩写,如 JAN、MAY 等;
    • 周是以周一为起点,定义为 1 ,剩下的依次类推,也就是说周日为7,当然更清晰的是使用星期的英文缩写,如 SUN、WED 等;
    • 年份的表示范围是 1970~9999,不过这个估计很少用了。
    • 补充一点,月或周使用英文时不区分大小写,所以 JAN 和 jAn 的结果是一样一样的。
    然后我们说一下通配符。所有的部分都支持 -、* 和 / 三种,日部分还额外支持 L、W 和 ?,周额外支持 L 和 ?。下面说一下各个通配符的含义:
    • *
      表示所有值. 例如:在分的字段上设置 “*”,表示每一分钟都会触发;
    • ?
      表示不指定值。使用的场景为不需要关心当前设置这个字段的值。例如:要在每月的10号触发一个操作,但不关心是周几,所以需要周位置的那个字段设置为“?”,具体设置为 0 0 0 10 * ?;

    • 表示区间。例如 在小时上设置“10-12”,表示 10、11、12 点都会触发;
    • ,
      表示指定多个值,例如在周字段上设置 “MON,WED,FRI” 表示周一,周三和周五触发;
    • /
      用于递增触发。如在秒上面设置 “5/15” 表示从 5 秒开始,每增 15 秒触发(5、20、35、50)。 在月字段上设置 “1/3” 所示每月 1 号开始,每隔三天触发一次;
    • L
      表示最后的意思,取英文 Last 的缩写。在日字段设置上,表示当月的倒数第几天(依据当前月份,如果是二月还会依据是否是闰年),如 L-2 代表从月份的最后一天开始倒数两天,L0 时,0可以省略,代表月份的最后一天。 在周字段上表示星期六,相当于 “7” 或 “SAT”。如果在 “L” 前加上数字,则表示该月最后一个星期几。例如在周字段上设置 “6L” 这样的格式,则表示 “本月最后一个星期五”;
    • W
      表示离指定日期的最近那个工作日(默认为周一至周五,你可以重新实现一个函数,然后设计 IsWorkDay 函数指针指向它来个性化工作日设置)。例如在日字段上设置 “15W”,表示离每月 15 号最近的那个工作日触发。如果 15 号正好是周六,则找最近的周五(14号)触发,,如果 15 号是周未,则找最近的下周一(16号)触发。如果15号正好在工作日(周一至周五),则就在该天触发。如果指定格式为 “1W”,它则表示每月 1 号往后最近的工作日触发。如果 1 号正是周六,则将在 3 号下周一触发。(注,”W”前只能设置具体的数字,不允许使用区间 “-”);
    • #
      序号(表示每月的第几个周几),例如在周字段上设置“6#3”,表示在每月的第三个周六。注意如果指定 “n#5”,而正好第五周没有周六,则不会触发该配置(用在母亲节和父亲节再合适不过了) ;
    好了,格式的说明也就这样子了,提示下 L 和 W 可以一起组合使用。如果在日字段上设置 LW ,则表示在本月的最后一个工作日触发。

    然后我们提供一些简单的掩码示例:
     
    Last edited: Aug 13, 2015
  2. swish

    swish Staff Member Moderater

    Apr 13, 2015
    [QWorker] 计划任务时,如果使用自己的工作日判定规则

    QWorker 中默认的工作日判定规则是周一至周五为工作日,周六和周日为休息日,大多数情况下,这没有多大的问题。但是,可但是,无论那个国家,都有自己的法定节假日,将它们放在工作日里显示是不合适的。而做为一个底层的引擎,显示不适合只根据中国的规则来写死。反过来,你也没法写死,因为像我们国家,许多法定节假日是按照农历走的,在阳历上的日期并不固定,显然这没法写死。

    所以,在 QWorker 中,提供了一个全局的 IsWorkDay 变量,用于关联到用户自定义的函数来判断指定的日期是否是工作日。其默认关联到了 DefaultIsWorkDay 函数,我们要改变它的行为,只需要编写一个自己的节假日判断函数,然后在程序启动时,设置 IsWorkDay 为自己的判断函数就可以了。

    这里介绍另一个东东:TQBits,你可以用它来最小化来存贮每年的休息日数据,每一位代表一天,然后 366 天存贮只需要 366 / 8 约 46 个字节(可提供368位)的大小即可。下面是一个简单的例子,其中 WorkDayMasks 是预存贮好的 TQBits 数组,每年对应 46 字节的工作日信息数据。
    Code (Text):
    1. functionMyIsWorkDay(ADate:TDateTime):Boolean;
    2.  
    3. begin
    4. Result:=WorkDayMasks[YearOf(ADate)][DayOfTheYear(ADate)];
    5. end;
    然后你在调用 QWorker.Plan 时的掩码中,如果包含了 W 掩码的时候,就会通过这个函数来判定某一天是否是工作日,从而决定是否调度作业。