定时任务框架Quartz.NET

1、介绍

Quartz.NET是一个开源的作业调度框架,非常适合在平时的工作中,定时轮询数据库同步,定时邮件通知,定时处理数据等。 Quartz.NET允许开发人员根据时间间隔来调度作业.

官方学习文档:http://www.quartz-scheduler.net/documentation/index.html

安装包:

1
2
3
<ItemGroup>
<PackageReference Include="Quartz" Version="3.9.0" />
</ItemGroup>

Quartz主要有三部分组成任务(Job)、触发器(Trigger)和调度器(Schedule)

Job就是执行的作业,Job需要实现IJob接口,实现Execute方法。Job中执行的参数从Execute方法的参数中获取。

触发器常用的有两种:SimpleTrigger触发器和CronTrigger触发器。

SimpleTrigger触发器: 实现简单业务,如每隔几分钟,几小时触发执行,并限制执行次数。

CronTrigger触发器:包含7个字段,秒 分 时 月内日期 月 周内日期 年(可选)

调度器就是将任务和触发器绑定,让触发器触发的时候去执行任务(调度器:负责创建,启动,停止和删除作业)。

工作流程:

1
2
3
scheduler是quartz的独立运行容器,trigger和job都可以注册在scheduler容器中,一个job可以有多个触发器,而一个触发器只能属于一个job。

Quartz中有一个调度线程QuartzSchedulerThread,调度线程可以找到将要被触发的trigger和job,然后在ThreadPool中获取一个线程来执行这个job。

2、JobFactory创建

创建job工厂,在IOC中放入Job,然后再工厂中获取容器中的Job对象。事实上,你实现工厂的核心就是定义**IJob**实现类的实例化规则!

创建一个QuartzService解决方案文件夹,在该文件夹中创建Cms.Quartz项目。

并且,在该项目中按照相应的包。

创建JobFactory类,实现IJobFactory接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class JobFactory : IJobFactory
{
private readonly IServiceProvider serviceProvider;
public JobFactory(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
// 从容器中获取Job实例。
var job = serviceProvider.GetService(bundle.JobDetail.JobType) as IJob;
return job!;
}

public void ReturnJob(IJob job)
{
var disposable = job as IDisposable;
if (disposable != null)
{
disposable.Dispose();
}
}
}

3、创建Job

这个类必须要继承IJob,实现具体的任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
namespace Cms.Quartz
{
// 添加该标签的目的,表示下面方法没有执行完成,就不会重复进来触发执行
[DisallowConcurrentExecution]
public class QuartzJob : IJob
{

private readonly MyDbContext myDbContext;
public QuartzJob(MyDbContext dbContext)
{
this.myDbContext = dbContext;
}
public async Task Execute(IJobExecutionContext context)
{
Console.WriteLine("具体执行的任务,统计用户注册数");
var list = await myDbContext.Users.Where(u => u.DelFlag == false).ToListAsync();
Console.WriteLine("userCount=" + list.Count());

// job详情
var jobDetails = context.JobDetail;
// 触发器的信息
var trigger = context.Trigger;
// 接收传递过过来的参数,参数都在JobDataMap中
JobDataMap dataMap = context.JobDetail.JobDataMap;

Console.WriteLine($"用户名是: {dataMap.GetString("UserName")}");
Console.WriteLine($"JobKey:{jobDetails.Key},Group:{jobDetails.Key.Group}\r\n" +
$"Trigger:{trigger.Key}\r\n" +
$"RunTime:{context.JobRunTime}" +
$"ExecuteTime:{DateTime.Now}");


}
}

4、创建调度器

创建一个调度器的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
namespace Cms.Quartz
{
/// <summary>
/// Quertz.Net 抽象调度中心
/// </summary>
public interface IControllerCenter
{
/// <summary>
/// 启动任务调度
/// </summary>
/// <returns></returns>
Task Start();
/// <summary>
/// 运行Job
/// </summary>
/// <returns></returns>
Task RunJob();
/// <summary>
/// 暂停Job
/// </summary>
/// <returns></returns>
Task PauseJob();
/// <summary>
/// 回复job
/// </summary>
/// <returns></returns>
Task ResumeJob();
}
}

实现上面的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
namespace Cms.Quartz
{
/// <summary>
/// Quartz.Net 实现任务调度
/// </summary>
public class ControllerCenter : IControllerCenter
{
private readonly IJobFactory jobFactory;
private Task<IScheduler> scheduler;
public ControllerCenter(IJobFactory jobFactory)
{
this.jobFactory = jobFactory;
scheduler = GetScheduler();
// 指定job工厂,调度器的实例会监控触发条件(触发器),当达到触发条件的时候,就会通知任务工厂,任务工厂根据JobType,从容器中获取对应的Job实例,并执行对应的Execute方法,执行具体的任务
scheduler.Result.JobFactory = jobFactory;
}

private Task<IScheduler> GetScheduler()
{
if (scheduler != null)
return scheduler;
else
{
/*
* 配置二进制策略
* https://blog.csdn.net/liyou123456789/article/details/126575055
*/
var properties = new NameValueCollection
{
["quartz.serializer.type"] = "binary"
};
// 从任务调度工厂获取调度的实例。
ISchedulerFactory sf = new StdSchedulerFactory(properties);
this.scheduler = sf.GetScheduler();
return scheduler;
}
}
/// <summary>
/// 暂停任务调度
/// </summary>
/// <returns></returns>
public async Task PauseJob()
{
var jobKey = new JobKey("job1", "group1");
if (await this.scheduler.Result.CheckExists(jobKey))
{
await this.scheduler.Result.PauseJob(jobKey);
Console.WriteLine("PauseJob Success!");
}
else
{
Console.WriteLine("Not IsExists JobKey");
}
}
/// <summary>
/// 恢复job
/// </summary>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task ResumeJob()
{
var jobKey = new JobKey("job1", "group1");
if (await this.scheduler.Result.CheckExists(jobKey))
{
await this.scheduler.Result.ResumeJob(jobKey);
Console.WriteLine("ResumeJob Success!");
}
else
{
Console.WriteLine("Not IsExists JobKey");
}
}
/// <summary>
/// 允许JOB
/// </summary>
/// <returns></returns>
public async Task RunJob()
{
var jobKey = new JobKey("job1", "group1");
// 第一次单击是没有对应的key。
if (await this.scheduler.Result.CheckExists(jobKey))
{
Console.WriteLine("JobKey Exists");
}
else
{
Console.WriteLine("JobKey Allow");
if (!this.scheduler.Result.IsStarted)
{
Console.WriteLine("quartz is not started");
await this.Start();
}
//定义作业详细信息并将其与QuartzJob任务相关联
//Quartz.NET中,我们使用调用器Scheduler, 将一个JobDetails对象关联到一个触发器中,而非直接关联一个Job对象到触发器对象中。原因是Job只是定义任务需要做什么
// Quartz还需要知道该Job实例所包含的属性;这将由JobDetail类来完成。
// 而JobDetail是Job的元数据,用于描述这个Job。它包括Job的实例和其他设置。简单来说,JobDetail是“怎么做”的描述,包括任务名、组名等
var job = JobBuilder.Create<QuartzJob>()
.UsingJobData("UserName", "zhangsan")//这里是给任务传递参数,如果需要传递多个参数可以多次调用UsingJobData
.WithIdentity("job1", "group1")
.Build();
var trigger = TriggerBuilder.Create()
.WithIdentity("trigger1", "group1")
.StartNow() //示作业现在就开始执行
.WithSimpleSchedule(a =>
{
a.RepeatForever();//永远执行
a.WithIntervalInSeconds(3);//执行间隔3s
})
.ForJob(job)
.Build();
// 将工作与触发器交给调度器
await this.scheduler.Result.ScheduleJob(job, trigger);
}
}
/// <summary>
/// 开启任务调度
/// </summary>
/// <returns></returns>
public async Task Start()
{
try
{
if (this.scheduler.Result.IsStarted)
{
Console.WriteLine("quartz is started");
}
else
{
Console.WriteLine("quartz start!");
await this.scheduler.Result.Start();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}

5、添加服务

Cms.Web这个Web项目中,添加服务。

1
2
3
4
5
6
7
8
9
// 处理邮件的队列
builder.Services.AddSingleton<MailQueueManager>();
//-------------自定义Job工厂
builder.Services.AddSingleton<IJobFactory, JobFactory>();
//----------------任务调度控制中心
builder.Services.AddSingleton<IControllerCenter, ControllerCenter>();
//-------------Jobs,将组件好的Job放在这里,
builder.Services.AddSingleton<QuartzJob>();

6、开启任务

创建QuartzController.cs控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class QuartzController : Controller
{
private readonly IControllerCenter _controllerCenter;
public QuartzController(IControllerCenter controllerCenter)
{
this._controllerCenter = controllerCenter;
}
/// <summary>
/// 开启任务调度
/// </summary>
/// <returns></returns>
[HttpGet]
public async Task<IActionResult> Start()
{
await _controllerCenter.Start();
return Content("开启任务调度");
}

/// <summary>
/// 运行Job
/// </summary>
/// <returns></returns>
[HttpGet]
public async Task<IActionResult> Run()
{
await _controllerCenter.RunJob();
return Content("开始运行JOB"); ;
}

public IActionResult Index()
{
return View();
}
}

先执行Start方法,然后再执行Run方法。

可以在系统的左侧添加一个菜单项,在对应的页面中添加相应的按钮来开启任务调度与开启JOB运行。

参考文档:https://www.cnblogs.com/qtiger/p/13633965.html

该文档中有关于CronTrigger触发器的详细说明