任务调度系统:自动化任务的实现与应用

任务调度系统在现代计算中扮演着至关重要的角色。无论是Windows的Task Scheduler,Linux的Crontab,还是Mac的Cron,它们的核心功能都是自动化执行预定任务。这些任务可以是启动应用程序、发送电子邮件、显示消息,或者备份敏感数据等。任务调度器通过监控用户设定的条件(称为触发器),并在条件满足时执行任务。

任务调度可以以多种方式进行,例如:

  • 在特定系统事件发生时
  • 在特定时间
  • 每天的特定时间
  • 每周的特定时间
  • 每月的特定时间
  • 每月的特定星期几
  • 当计算机进入空闲状态时

随着Web应用程序的发展,客户端变得越来越复杂,它们需要处理用户输入、管理后台任务,功能上越来越接近操作系统。在这种情况下,任务调度器的作用变得尤为重要。

学习与实践

任务调度系统的数据模型由两部分组成:调度器和任务管理。调度器负责解析、创建、编辑基于Crontab格式的字符串,并计算下一个和上一个预定日期,同时准备人类可读的事件字符串。

额外的功能是计算所有下一个和上一个预定日期,直到用户指定的日期。调度器数据结构由调度器类和六个辅助类组成:

  • minuteContainer
  • hourContainer
  • dayContainer
  • monthContainer
  • weekContainer
  • baseContainer

baseContainer封装了通用功能,是其他容器的超类。调度器操作基于Crontab格式的字符串,格式如下:

// JavaScript .------------------- minute (0-59) | .---------------- hour (0-23) | | .------------- day of month (1-31) | | | .---------- month (1-12) | | | | .------- day of week (0-7) (Sunday=0 or 7) | | | | | * * * * *

根据模式,分钟、小时、天、月份和星期几的容器将被创建,并记住值。容器是基于输入数据和一组方法形成的数组,用于操作它们。数组的值和分布应该与输入模式相对应,并且数组的值应该可以通过索引轻松检索。

例如,如果有模式“*/5 2/3 * * *”,将根据第一个参数*/5创建分钟容器,其值为[0, 5, 5, 5, 5, 5, 10, 10, 10, 10, 10, 15, 15, 15, 15, 15, …, 55, 55, 55, 55, 55]。这允许在计算下一个发生时避免循环。为了获得下一个发生,只需要通过当前日期的索引检索数组的值。例如,对于日期08/12/2015 (15:03),当前分钟为03,在容器中索引03的值将为5,因此下一个发生将是08/12/2015 (17:05),因为它每3小时发生一次,从2开始,每5分钟开始一次。

可能会产生一个问题,为什么它操作的是分钟、小时等的哈希值数组,而不是毫秒。两种方式都是可能的。也许当前解决方案首先出现在脑海中,并且看起来更简单。

当前调度器的实现几乎接受所有格式的Crontab字符串,所以它可以是"*/5 2/3 * * *","20/5 2/3 1-2,4-7 * *","1,2,3,4,5 2/3 1-2,4-7 * *"。它还有方法通过添加或删除数组中的元素来形成容器,例如:

// JavaScript addMinute(m), removeMinute(m), ...

此外,调度器打印出人类可读的字符串,如下所示:“每3小时发生一次,从02:00开始,每天,每个月。”不幸的是,目前还没有本地化。

另一个可能有趣的功能是计算直到特定日期的所有发生。为了避免在计算非常详细的模式(例如“*/5 * * * *”)期间一年内的所有日期时浏览器冻结,操作被分成小块,并且使用计时器延迟处理每个块,以便浏览器有时间切换到不同的任务。默认的块大小是100个元素,或者可以通过传递任何数字作为函数getAllNextTill(...)的第三个参数来指定自己的块大小。

// JavaScript /* set maxSyncLoadEl to -1 to have traditional loop */ this.getAllNextTill = function(endDate, callback, maxSyncLoadEl) { var _allDates = Array(); var _maxSyncLoadEl = typeof maxSyncLoadEl != 'undefined' && scheduler.isNumber(maxSyncLoadEl) ? maxSyncLoadEl : maxSyncLoadElDef; var _callback = callback; var _startDate = startDate; var _syncLoadedEl = 0; if (_startDate != null && _startDate instanceof Date) { var scope = this; (function loop() { try { while (_maxSyncLoadEl == -1 || _syncLoadedEl <= _maxSyncLoadEl) { if (scope.previous()) { var curDate = scope.getDateStamp(); if (_startDate < curDate) { _allDates.push(curDate); } else { if (callback) { callback(_allDates); } return; } } _syncLoadedEl++; } _syncLoadedEl = 0; setTimeout(loop, 1); } catch (e) { throw e; } })(); } else { throw new InvalidArgumentException(); } }

taskManger和task对象负责根据调度触发任务。taskManager封装了管理任务的方法。task对象封装了在特定时间运行作业的方法,该时间由调度器设置,或者可以立即强制启动。

// JavaScript this.start = function() { _isStarted = true; continuouslyRun(true); }; function continuouslyRun(skip) { if (_isStarted) { if (!skip) { runOnce(); } // schedule next time of running var currDate = new Date(); _scheduler.setCurrentTimeExt(currDate); if (_scheduler.next()) { var nextDate = _scheduler.getDateStamp(); var timeout = nextDate.getTime() - currDate.getTime(); _timerId = setTimeout(continuouslyRun, timeout); } } }; function runOnce() { _isRunning = true; if (_action) { if (!_args) { _action(scope); } else { _action(scope, _args); } } _isRunning = false; };

数据模型示例

// JavaScript var tasks = new scheduler.taskManager(); tasks.addTask(new scheduler.task("0/2 * * * *", function(task, args) { // to do something }, "done")); tasks.addTask(new scheduler.task("0/10 * * * *", function(task, args) { // to do something }, "done")); var _task = new scheduler.task("0/5 * * * *", function(task, args) { // to do something }, "done"); tasks.addTask(_task); tasks.enumerate(function(task, args) { // to do something }); tasks.start(); // to remove task from scheduling tasks.removeTask(_task);
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485