一 、使用背景
我们在项目中经常会用到定时计划任务,传统的实现方法总结起来有三种:
- 通过winform的timer控件;
- 控制台+windows计划任务;
- 创建windows service。
但以上几种均可以实现简单的定时任务,但缺点也显而易见,前两种在执行期间需要客户端一直运行,而如果此时恰好有处女座登录服务器,搞不准就给干掉了;windows service相对就比较正规了,不过安装的繁琐和配置不够灵活也倒逼一好多懒人采用第2种方式。
那么有没有什么方法既能很方便地安装又能很灵活地配置任务呢,这就引出了本文的主旨。
二、框架简介
Topshelf是一个windows service框架,它对windows service进行封装使得我们可以把控制台程序安装成service,便捷的安装以及控制台可视化的调试大大提升了服务的开发效率。以上是我个人理解,如有错误,还望大侠纠正,不胜感激!
Quartz.Net是一款针对.net推出的开源作业调度框架,网上关于它的描述举不胜举,个人建议还是看英文原版网站:Quartz.NET - Documentation
先总结一下Quartz的三个概念:
- Schedule:任务调度器,这是一个抽象的概念,相当于整个框架的CPU,负责对Trigger和Job统筹执行;
- Trigger:job触发条件,比如每天固定时间执行、每隔多少时间执行等;
- Job:处理需要执行的任务,也就是我们代码要处理的具体业务。
三、代码实践
1.TopShelf搭建
(1)安装程序包:右键引用-打开nuget管理程序包,如下图,点击安装
(2)安装完成即可自动完成对Topshelf.dll的引用
(3)控制台代码如下:
using Quartz;
using Quartz.Impl;
using Quartz.Simpl;
using Quartz.Xml;
using Topshelf;
namespace JobSchedule
{
class Program
{
static void Main(string[] args)
{
HostFactory.Run(x =>
{
x.Service<TownCrier>(s =>
{
s.ConstructUsing(name => new TownCrier());
s.WhenStarted(tc => tc.Start());
s.WhenStopped(tc => tc.Stop());
});
x.RunAsLocalSystem();
x.SetDescription("QuartzNet任务调度服务,灵活配置任务计划");
x.SetDisplayName("QuartzJobShedule");
x.SetServiceName("Quartz任务调度框架");
});
}
}
/// <summary>
/// 服务主体(英文原意:在城镇中沿街呼叫传报公告的人)
/// </summary>
public class TownCrier
{
private IScheduler scheduler;
public TownCrier()
{
//初始化相关配置
}
public void Start()
{
//启动服务
}
public void Stop()
{
//关闭服务
}
}
}
注释:主线程Main函数中调用HostFactory的Run方法,委托调用service,完成服务的初始化绑定,老司机可能会惊呼,这完全就是service创建的时候需要配置的东西嘛。这里需要注意ConstructUsing方法中的委托代码,这里实例化一个实例TownCrier类。TownCrier类中需要实现服务启动Start和服务关闭Stop方法,和windows服务的方法很相似,这里不再赘述。
2.引入QuartzNet实现任务调度
1.安装Quartz
管理Nuget程序包,安装后即多了Quartz.dll的引用。
2.更新TownCrier的代码如下
public class TownCrier
{
private IScheduler scheduler;
public TownCrier()
{
//从配置中读取计划执行策略
XMLSchedulingDataProcessor processor = new XMLSchedulingDataProcessor(new SimpleTypeLoadHelper());
ISchedulerFactory sf = new StdSchedulerFactory();
scheduler = sf.GetScheduler();
processor.ProcessFileAndScheduleJobs("~/quartz_jobs.xml", scheduler);//quartz_jobs.xml文件路径
}
public void Start()
{
scheduler.Start();//启动quartz服务
}
public void Stop()
{
scheduler.Shutdown(false);//true:等待正在运行的计划任务执行完毕;false:强制关闭
}
}
计划任务启动需要初始化schedule配置,可以放在构造中;然后再start和stop方法中启动和关闭quartz服务
3.计划任务实现类代码
using System;
using Quartz;
namespace JobSchedule
{
public class TestJob: IJob
{
/// <summary>
/// 实现Ijob接口
/// </summary>
/// <param name="context"></param>
public void Execute(IJobExecutionContext context)
{
//job具体实现
Console.WriteLine("hello job");
}
}
}
Execute方法中添加业务逻辑代码,建议从项目中分离,降低耦合。
4.quartz_jobs.xml配置文件
<?xml version="1.0" encoding="utf-8" ?>
<job-scheduling-data xmlns="http://quartznet.sourceforge.net/JobSchedulingData">
<processing-directives>
<overwrite-existing-data>true</overwrite-existing-data>
</processing-directives>
<schedule>
<job>
<!--job名称-->
<name>JobName</name>
<!--job分组-->
<group>JobGroup</group>
<!--描述-->
<description>job描述</description>
<!--job方法所在命名空间.方法所在类,方法所在类-->
<job-type>JobSchedule.TestJob,TestJob</job-type>
<!--true:始终保留在Quartz的JobStore中;false:如果未关联trigger则移除-->
<durable>true</durable>
<recover>false</recover>
</job>
<!--复杂任务触发器-->
<trigger>
<cron>
<!--trigger名称-->
<name>TrigName</name>
<!--trigger分组-->
<group>TrigGroup</group>
<!--描述-->
<description>tirgger描述</description>
<!--绑定的job名称-->
<job-name>JobName</job-name>
<!--绑定的job组-->
<job-group>JobGroup</job-group>
<!--未按时触发时采取的策略-->
<misfire-instruction>SmartPolicy</misfire-instruction>
<!--每个整点触发-->
<cron-expression>0 0 0/1 * * ? </cron-expression><!--秒 分 时 天 月 (周) 年-->
</cron>
</trigger>
</schedule>
</job-scheduling-data>
配置文件包括两个大的节点job和trigger,各自描述自身的一些配置,注意两点:
- job-type必须指明job所在的程序集
- trigger中的job-name和job-group必须与需要调度的job中的name和group一致
cron-expression配置请点击cron-expression语法及帮助或者可以使用在线生成工具cron语法生成器
至此,我们已经搭建了一个简易的可以自由调度的任务框架,本篇重在动手实践,有一些原理讲解不到位还请指正批评。但人生的意义不就在于入门后的自我修行深入么?