维基百科上是这样描述计划任务的:
“Cron is a time-based job scheduler in Unix-like computer operating systems. Cron is short for Chronograph. Cron enables users to schedule jobs (commands or shell scripts) to run automatically at a certain time or date.”
有些场景下你需要定时的重复调度某些操作,下面是一些定时的例子:
- 每15分钟
- 每0,15,30,45 分钟
- 每天12 点
- 每天的12AM, 6AM, 12PM, 6PM
- 星期六的 12PM
- 每个月的第一天
在这片文章里,我将会介绍使用C#实现计划任务。
前人做的工作
当我在工作中遇到这样的需求时,我的第一想法就是去网上看看,不是有句话嘛,不要重复发明轮子。根据网上的资料显示很多人已经在C#实现计划任务上做了很多工作。但是多数不符合我的要求,还有一部分实现的比较复杂。我就想做一个干干净净、清清爽爽的库,自己用。下面是我参考比较多的两个库。
- Quartz.NET – 这个库比较全,其实他是一个线程调度库,比我要求的要全得多。
- NCrontab – 这个库比较简单,实现的是一个工作调度器,缺少解析cron的部分,不适合我使用。
我在NCrontab的基础上增加了cron表达式解析功能,使用事件触发完成预设工作的执行,完成了我的计划任务库。
如何使用我的代码库?
下面举一个例子来说明如何使用我的代码库完成建立一个计划任务:
// Create the cron schedule. // CronExpression cronExpression = CronBuilder.CreateHourlyTrigger(new int[] {0, 30}); CronSchedule cronSchedule = CronSchedule.Parse(cronExpression.ToString()); List<CronSchedule> cronSchedules = new List<CronSchedule> { cronSchedule }; // Create the data context for the cron object. // CronObjectDataContext dc = new CronObjectDataContext { Object = myObject, CronSchedules = cronSchedules, LastTrigger = DateTime.MinValue }; // Create the cron object. // CronObject cron = new CronObject(dc); // Register for events. // cron.OnCronTrigger += Cron_OnCronTrigger; // Start the cron job. // cron.Start();
首先建立一个调度器,在每小时的0分钟和30分钟触发事件,事件响应函数里面可以做多项工作。. ‘CronBuilder’ 类可以辅助你建立计划任务,你也可以使用cron表达式完成计划任务的建立,上面例子中的任务可以使用 “0,30 * * * *” 或者 “*/30 * * * *”表示,相比之下使用CronBuilder使得工作更简单。
当计划任务启动后,到了指定时间‘OnCronTrigger’事件将会触发,事件响应函数将会执行:
private void Cron_OnCronTrigger(CronObject cronObject) { IRunnable myObject = cronObject.Object as IRunnable; myObject.Run(); }
cronObject类表示计划工作,其中包含可执行的方法。
调度器Scheduler
‘CronSchedule’ 类表示调度器,其Parse方法解析计划任务表达式。这个类有一个私有构造函数,使用重载的Parse方法可以实例化一个新的实例。
其中调度的算法是首先计算出计划时间与当前时间的间距,然后在当前一分钟内自旋等待,然后检查是否到达触发时刻,如果没有将已经自旋时间增加一分钟,继续下一分钟自旋,自旋满一个小时后,增加已经自旋的小时数,如此递增,自旋时间等于已经算出的时间间距。
基于事件的任务触发
‘CronObjectDataContext 类包含三个属性.
- CronSchedules – 一个事件可以包含多个调度器,也即是可以有多重触发机制.
- LastTrigger – 这里记录了上次触发的时间.
- Object – 此属性表示将要执行的任务.
‘CronObject’类包含两个公开方法: Start 和 Stop。用来启动和停止任务触发:
public bool Start() { lock (_startStopLock) { // Can't start if already started. // if (_isStarted) { return false; } _isStarted = true; _isStopRequested = false; // This is a long running process. Need to run on a thread // outside the thread pool. // _thread = new Thread(ThreadRoutine); _thread.Start(); } // Raise the started event. // if(OnStarted != null) { OnStarted(this); } return true; } public bool Stop() { lock (_startStopLock) { // Can't stop if not started. // if (!_isStarted) { return false; } _isStarted = false; _isStopRequested = true; // Signal the thread to wake up early // _wh.Set(); // Wait for the thread to join. // if(!_thread.Join(5000)) { _thread.Abort(); // Raise the thread abort event. // if(OnThreadAbort != null) { OnThreadAbort(this); } } } // Raise the stopped event. // if(OnStopped != null) { OnStopped(this); } return true; }.
线程routine主要负责触发OnCronTrigger事件,其中使用EventWaitHandle来实现在下一个调度触发来临前线程等待。在Stop方法中也是使用这种机制来保证所有调度器全部停止的。
private readonly EventWaitHandle _wh = new AutoResetEvent(false); private void ThreadRoutine() { // Continue until stop is requested. // while(!_isStopRequested) { // Determine the next cron trigger // DetermineNextCronTrigger(out _nextCronTrigger); TimeSpan sleepSpan = _nextCronTrigger - DateTime.Now; if(sleepSpan.TotalMilliseconds < 0) { // Next trigger is in the past. Trigger the right away. // sleepSpan = new TimeSpan(0, 0, 0, 0, 50); } // Wait here for the timespan or until I am triggered // to wake up. // if(!_wh.WaitOne(sleepSpan)) { // Timespan is up...raise the trigger event // if(OnCronTrigger != null) { OnCronTrigger(this); } // Update the last trigger time. // _cronObjectDataContext.LastTrigger = DateTime.Now; } } }
DetermineNextCronTrigger方法决定哪个调度器将被拿出来负责调度。
private void DetermineNextCronTrigger(out DateTime nextTrigger) { nextTrigger = DateTime.MaxValue; foreach (CronSchedule cronSchedule in _cronObjectDataContext.CronSchedules) { DateTime thisTrigger; if(cronSchedule.GetNext(LastTigger, out thisTrigger)) { if (thisTrigger < nextTrigger) { nextTrigger = thisTrigger; } } } }
累死我了,终于说完了。大家可是试用一下。
猛击下载链接
转载请注明:源自极客飞逸(http://www.fiiii.com)