使用C#实现计划任务(corn job)

维基百科上是这样描述计划任务的:

“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

posted @ 2013-06-27 23:22  njuxdj  阅读(6977)  评论(0编辑  收藏  举报