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

“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

使用C#实现计划任务(corn job)的更多相关文章

  1. C#编写一个在asp.net core 3.1下的简单的corn模式的计划任务和一个更简单的定时器类

    asp.net core 下,新增了一个BackgroundService用来实现能在后台跑一个长久运行的任务,因此,也可以用来替换掉原来使用的static的Timer组件, Timer组件主要有以下 ...

  2. corn计划周期任务

                                                                             corn计划任务 1.计划任务有四种方式   cron ...

  3. Quartz中时间表达式的设置-----corn表达式

    Quartz中时间表达式的设置-----corn表达式 时间格式: <!-- s m h d m w(?) y(?) -->,   分别相应: 秒>分>小时>日>月 ...

  4. Quartz中时间表达式的设置-----corn表达式 (转)(http://www.cnblogs.com/GarfieldTom/p/3746290.html)

    Quartz中时间表达式的设置-----corn表达式 (注:这是让我看比较明白的一个博文,但是抱歉,没有找到原作者,如有侵犯,请告知) 时间格式: <!-- s m h d m w(?) y( ...

  5. Quartz中时间表达式的设置-----corn表达式 (转)

    Quartz中时间表达式的设置-----corn表达式 (注:这是让我看比较明白的一个博文,但是抱歉,没有找到原作者,如有侵犯,请告知) 时间格式: <!-- s m h d m w(?) y( ...

  6. webapi + windows计划 + mshta 实现定时执行任务

    当然,实现定时任务有更好的操作方式,比如方式一:asp.net mvc+quartz.net +corn +webapi,asp.net mvc做任务管理的平台,使用CronTrigger做定时触发, ...

  7. 在Spring3中使用注解(@Scheduled)创建计划任务

    Spring3中加强了注解的使用,其中计划任务也得到了增强,现在创建一个计划任务只需要两步就完成了: 创建一个Java类,添加一个无参无返回值的方法,在方法上用@Scheduled注解修饰一下: 在S ...

  8. 状态压缩dp初学__$Corn Fields$

    明天计划上是要刷状压,但是作为现在还不会状压的\(ruoruo\)来说是一件非常苦逼的事情,所以提前学了一下状压\(dp\). 鸣谢\(hmq\ juju\)的友情帮助 状态压缩动态规划 本博文的大体 ...

  9. Linux centos7 linux任务计划cron、chkconfig工具、systemd管理服务、unit介绍、 target介绍

    一.linux任务计划cron crontab -u  -e -l -r 格式;分 时 日 月 周 user command 文件/var/spool/corn/username 分范围0-59,时范 ...

随机推荐

  1. Ant junitreport with Maven

    大家可能都知道在Ant里可以使用junit和junitreport两个task来完成对测试结果生成HTML格式的报告. Maven里的Surefire-report的插件只能对Java测试报告支持的比 ...

  2. spring boot 很好的文章

    http://blog.csdn.net/isea533/article/details/50278205

  3. git push fatal: HttpRequestException encountered

    原因: github禁用了TLS1.0/1.1协议 截至2018年2月22日,GitHub禁用了对弱加密的支持,这意味着许多用户会突然发现自己无法使用Git for Windows进行身份验证(影响版 ...

  4. D - Frogger

    Freddy Frog is sitting on a stone in the middle of a lake. Suddenly he notices Fiona Frog who is sit ...

  5. Python 错误总结

    1.以一种访问权限不允许的方式做了一个访问套接字的尝试. 解决方法:这个问题缘由是有端口被占用

  6. 缓冲区 粘包 029 send 和sendall 的区别 find 和 findall 的区别

    一.tcp : 属于长连接 与客户端连接了之后 其他客户端需要等待 要连接另外一个 必须优雅的断开前面这个客户的连接. 二.缓冲区 :为了避免网络传输信号不通畅而是程序一直停留在消息发送状态而不向下进 ...

  7. itchat教程

    https://www.python.org/ftp/python/3.6.6/ https://www.python.org/ftp/python/3.6.6/Python-3.6.6.tar.xz ...

  8. sofa-boot(1)helloworld项目

    本示例参考:https://www.sofastack.tech/sofa-boot/docs/QuickStart 示例采用sofa-boot 3.1.1版本. 如下步骤: 1.进入https:// ...

  9. Spyder清除Variable Explorer&&手动安装protobuf3.0(为了配置windows的python接口)

    输入:reset 选择:y PS:建议在windows下,安装anaconda32bit版本的,可以兼容更多第三方包.   Conda使用清华镜像 配置镜像 在conda安装好之后,默认的镜像是官方的 ...

  10. java——极简handler机制

    handler机制要做的事情: 1.把一堆从四面八方传来的message加到一个队列中,这个队列就是MessageQueue. 2.将MessageQueue中的队头Message取出,并使用这个me ...