作業(yè)調(diào)度框架Quartz.net用法詳解
一、介紹
Quartz.NET是一個強大、開源、輕量的作業(yè)調(diào)度框架,你能夠用它來為執(zhí)行一個作業(yè)而創(chuàng)建簡單的或復雜的作業(yè)調(diào)度。它有很多特征,如:數(shù)據(jù)庫支持,集群,插件,支持cron-like表達式等等。在2017年的最后一天Quartz.NET 3.0發(fā)布,正式支持了.NET Core 和async/await。這是一個大版本,有眾多新特性和大的功能
官網(wǎng):http://www.quartz-scheduler.net/
源碼:https://github.com/quartznet/quartznet
示例:https://www.quartz-scheduler.net/documentation/quartz-3.x/quick-start.html
新功能
- 支持 async/await 基于任務(wù)的作業(yè),內(nèi)部以async/await工作
- 支持.NET Core / netstandard 2.0和.NET Framework 4.5.2及更高版本
- 通過提供程序名稱SQLite-Microsoft支持Microsoft.Data.Sqlite,舊的提供程序SQLite也仍然有效,還可以用
- 增加了對SQL Server內(nèi)存優(yōu)化表的初步支持和Quartz.Impl.AdoJobStore.UpdateLockRowSemaphoreMOT
- 從依賴關(guān)系中刪除Common.Logging
- 刪除C5 Collections,使用.NET框架內(nèi)置的Collections
- 在插件啟動時添加對作業(yè)調(diào)度XML文件的驗證
- 在TimeZoneUtil中添加對額外自定義時區(qū)解析器功能的支持
Quartz3.x和2.x最大的優(yōu)勢在于:支持異步執(zhí)行Job,所以建議將Job的Excute方法設(shè)計為異步方法,這樣做可以提高任務(wù)調(diào)度和執(zhí)行效率。
quartz的構(gòu)成和基本工作流程
在使用Quart.net前,我們先理解下quartz的構(gòu)成和基本工作流程,
1、Quartz包含以下五個基本部分:
- Scheduler 調(diào)度器,quartz工作時的獨立容器
- Trigger 觸發(fā)器,定義了調(diào)度任務(wù)的時間規(guī)則
- Job 調(diào)度的任務(wù)
- ThreadPool 線程池(不是clr中的線程池),任務(wù)最終交給線程池中的線程執(zhí)行
- JobStore RAWStore和DbStore兩種,job和trigger都存放在JobStore中
2、Quartz的基本工作流程:
scheduler是quartz的獨立運行容器,trigger和job都可以注冊在scheduler容器中。
其中trigger是觸發(fā)器,用于定義調(diào)度任務(wù)的時間規(guī)則,job是被調(diào)度的任務(wù),一個job可以有多個觸發(fā)器,而一個觸發(fā)器只能屬于一個job。
Quartz中一個調(diào)度線程QuartzSchedulerThread,調(diào)度線程可以找到將要被觸發(fā)的trigger,通過trigger找到要執(zhí)行的job,然后在ThreadPool中獲取一個線程來執(zhí)行這個job。
JobStore主要作用是存放job和trigger的信息。
二、基于文件配置
Quartz.NET 在3.x已經(jīng)將插件分離了,所以如果要從xml直接加載文件,需要引入插件包
1、引入包

2、配置
app.config
<xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="quartz" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</configSections>
<quartz>
<add key="quartz.scheduler.instanceName" value="QuartzScheduler"/>
<add key="quartz.threadPool.type" value="Quartz.Simpl.SimpleThreadPool, Quartz"/>
<add key="quartz.threadPool.threadCount" value="10"/>
******************************Plugin配置*********************************************
<add key="quartz.plugin.jobInitializer.type" value="Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin, Quartz.Plugins"/>
<add key="quartz.plugin.jobInitializer.fileNames" value="quartz_jobs.xml"/>
</quartz>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2"/>
</startup>
</configuration>quartz_jobs.xml
<xml version="1.0" encoding="UTF-8"?>
This file contains job definitions in schema version 2.0 format
<job-scheduling-data xmlns="http://quartznet.sourceforge.net/JobSchedulingData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0">
<processing-directives>
<overwrite-existing-data>true</overwrite-existing-data>
</processing-directives>
<schedule>
開始執(zhí)行一個調(diào)度
<job>
<name>jldwjob</name>
<group>kelun</group>
<description>計量單位</description>
格式:實現(xiàn)了IJob接口的包含完整命名空間的類名,程序集名稱
<durable>true</durable>
<recover>false</recover>
</job>
<trigger>
<cron>
<name>jlwd</name>
<group>kelun</group>
<job-name>jldwjob</job-name>
<job-group>kelun</job-group>
2018-01-22T00:00:00+08:00
每3秒執(zhí)行一次
</cron>
</trigger>
</schedule>
</job-scheduling-data>3、接口實現(xiàn)
using Kelun.Log4Net;
using Quartz;
using System.Reflection;
using System.Threading.Tasks;
namespace Kelun
{
class JldwJob : IJob
{
private static readonly IMyLog Logger = MyLogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
public Task Execute(IJobExecutionContext context)
{
return Task.Run(() =>
{
Logger.Info("定時任務(wù)執(zhí)行");
});
}
}
}4、啟動Quartz
public MainForm()
{
InitializeComponent();
RunProgramAsync().GetAwaiter().GetResult();
}
private static async Task RunProgramAsync()
{
try
{
StdSchedulerFactory factory = new StdSchedulerFactory();
IScheduler scheduler = await factory.GetScheduler();
//開啟調(diào)度器
await scheduler.Start();
//創(chuàng)建一個作業(yè)
//IJobDetail job = JobBuilder.Create().WithIdentity("myJob", "group1").Build();
//ITrigger trigger = TriggerBuilder.Create()
// .WithIdentity("myTrigger", "group1")
// .StartNow() //現(xiàn)在開始
// .WithSimpleSchedule(x => x
// .WithIntervalInSeconds(1) //觸發(fā)時間,1秒一次
// .RepeatForever())
// .Build();
//把作業(yè),觸發(fā)器加入調(diào)度器。
//await scheduler.ScheduleJob(job, trigger);
//await scheduler.Shutdown();
}
catch (SchedulerException se)
{
Logger.Error("執(zhí)行錯誤", se);
}
}5、禁用Quartz.NET日志輸出
<logger name="Quartz">
<level value="OFF" />
</logger>6、當應(yīng)用或網(wǎng)站關(guān)閉時結(jié)束正在執(zhí)行的工作
在Global.asax中的Application_End方法中添加如下代碼:
protected void Application_End(object sender, EventArgs e)
{
// 在應(yīng)用程序關(guān)閉時運行的代碼
if (scheduler != null)
{
scheduler.Shutdown(true);
}
}三、基于代碼的方式
用五分鐘看一個簡單的例子吧,這個例子中通過調(diào)度器工廠StdSchedulerFactory獲取一個調(diào)度器實例scheduler,然后定義Job和trigger,并注冊到調(diào)度器中,最后啟動scheduler就可以執(zhí)行我們的任務(wù)了。代碼如下:
using Quartz;
using Quartz.Impl;
using System;
using System.Threading.Tasks;
namespace ConsoleApp3
{
internal class Program
{
private static void Main(string[] args)
{
//調(diào)度器,生成實例的時候線程已經(jīng)開啟了,不過是在等待狀態(tài)
StdSchedulerFactory factory = new StdSchedulerFactory();
IScheduler scheduler = factory.GetScheduler().Result;
//創(chuàng)建一個Job,綁定MyJob
IJobDetail job = JobBuilder.Create() //獲取JobBuilder
.WithIdentity("jobname1", "group1") //添加Job的名字和分組
.WithDescription("一個簡單的任務(wù)") //添加描述
.Build(); //生成IJobDetail
//創(chuàng)建一個觸發(fā)器
ITrigger trigger = TriggerBuilder.Create() //獲取TriggerBuilder
.StartAt(DateBuilder.TodayAt(01, 00, 00)) //開始時間,今天的1點(hh,mm,ss),可使用StartNow()
.ForJob(job) //將觸發(fā)器關(guān)聯(lián)給指定的job
.WithPriority(10) //優(yōu)先級,當觸發(fā)時間一樣時,優(yōu)先級大的觸發(fā)器先執(zhí)行
.WithIdentity("tname1", "group1") //添加觸發(fā)器的名字和分組
.WithSimpleSchedule(x => x.WithIntervalInSeconds(1) //調(diào)度,一秒執(zhí)行一次,執(zhí)行三次
.WithRepeatCount(3)
.Build())
.Build();
//start讓調(diào)度線程啟動【調(diào)度線程可以從jobstore中獲取快要執(zhí)行的trigger,然后獲取trigger關(guān)聯(lián)的job,執(zhí)行job】
scheduler.Start();
//將job和trigger注冊到scheduler中
scheduler.ScheduleJob(job, trigger).Wait();
Console.ReadKey();
}
}
#region MyJob
///
/// 一個簡單的Job,所有的Job都要實現(xiàn)IJob接口
///
public class MyJob : IJob
{
public async Task Execute(IJobExecutionContext context)
{
await Task.Run(() =>
{
Console.WriteLine("hello quartz!");
//JobDetail的key就是job的分組和job的名字
Console.WriteLine($"JobDetail的組和名字:{context.JobDetail.Key}");
Console.WriteLine();
});
}
}
#endregion MyJob
}通過Quartz來調(diào)度一個任務(wù)十分簡單,執(zhí)行結(jié)果如下:

四、Quartz.net的概念和基本用法
1.TriggerBuilder介紹
在Quartz中Trigger的作用是定義Job何時執(zhí)行。
Quartz.net提供了四種觸發(fā)策略:SimpleSchedule,CalendarIntervalSchedule,DailyTimeIntervalSchedule和CronSchedule。
TriggerBuilder顧名思義就是用來創(chuàng)建Trigger的。
1、SimpleSchedule
Simpleschedule 是最簡單的一種觸發(fā)策略,它的作用類似于timer,可以設(shè)置間隔幾秒/幾分鐘/幾小時執(zhí)行一次,如創(chuàng)建一秒執(zhí)行一次的觸發(fā)器如下
ITrigger trigger = TriggerBuilder.Create()
.StartNow()
.WithIdentity("tname1", "group1")
.WithSimpleSchedule(x => x.WithIntervalInSeconds(1) //設(shè)置時間間隔,時分秒
.WithRepeatCount(3)) //設(shè)置執(zhí)行次數(shù),總共執(zhí)行3+1次,
.Build();2、CalendarIntervalSchedule
CalendarIntervalSchedule擴展了Simplescheduler的功能,Simplescheduler只能在時分秒的維度上指定時間間隔,那么就有一個問題,如果我們想一個月執(zhí)行一次怎么辦呢?要知道每個月的天數(shù)是不一樣的,用SimpleSchedule實現(xiàn)起來就十分麻煩了。
CalendarIntervalSchedule可以實現(xiàn)時分秒天周月年的維度上執(zhí)行輪詢。如創(chuàng)建一個月執(zhí)行一次的觸發(fā)器如下:這樣
ITrigger trigger = TriggerBuilder.Create()
.StartAt(DateTimeOffset.Parse("2018/1/6 13:17:00"))
.WithIdentity("tname1", "group1")
.WithCalendarIntervalSchedule(x => x.WithIntervalInMonths(1)) //一月執(zhí)行一次
.Build();3、DailyTimeIntervalSchedule
DailyTimeIntervalSchedule主要用于指定每周的某幾天執(zhí)行,如我們想讓每周的周六周日的8:00-20:00,每兩秒執(zhí)行一次,創(chuàng)建觸發(fā)器如下:
ITrigger trigger =TriggerBuilder.Create()
.StartAt(DateTimeOffset.Parse("2018/1/6 13:17:00"))
.WithIdentity("tname1", "group1")
.WithDailyTimeIntervalSchedule(x => x.OnDaysOfTheWeek(new DayOfWeek[] { DayOfWeek.Saturday, DayOfWeek.Sunday }) //周六和周日
.StartingDailyAt(TimeOfDay.HourMinuteAndSecondOfDay(8, 00, 00)) //8點開始
.EndingDailyAt(TimeOfDay.HourMinuteAndSecondOfDay(20, 00, 00)) //20點結(jié)束
.WithIntervalInSeconds(2) //兩秒執(zhí)行一次,可設(shè)置時分秒維度
.WithRepeatCount(3)) //一共執(zhí)行3+1次
.Build();4、CronSchedule
CronSchedule是應(yīng)用最多的觸發(fā)策略,通過Cron表達是我們可以輕松地表示任意的時間節(jié)點,下邊的代碼創(chuàng)建了一個每隔5秒執(zhí)行一次的觸發(fā)器
ITrigger trigger = TriggerBuilder.Create()
.StartAt(DateTimeOffset.Parse("2018/1/6 13:17:00"))
.WithIdentity("tname1", "group1")
.WithCronSchedule("3/5 * * * * ?") //五秒執(zhí)行一次
.Build();5、Cron表達式
cron表達式有七個部分組成,以此是秒、分、時、天、月、周、年,其中年是可選的。Cron表達式對特殊字符的大小寫不敏感,對代表星期的縮寫英文大小寫也不敏感。

- 星號(*):可用在所有字段中,表示對應(yīng)時間域的每一個時刻。例如, 在分鐘字段時,表示“每分鐘”;
- 問號(?):該字符只在日期和星期字段中使用,它通常指定為“無意義的值”,相當于點位符;
- 減號(-):表達一個范圍,如在小時字段中使用“10-12”,則表示從10到12點,即10,11,12;
- 逗號(,):表達一個列表值,如在星期字段中使用“MON,WED,FRI”,則表示星期一,星期三和星期五;
- 斜杠(/):x/y表達一個等步長序列,x為起始值,y為增量步長值。如5/15在分鐘字段中表示5,20,35,50;
- L:該字符只在日期和星期字段中使用,代表“Last”的意思,但它在兩個字段中意思不同。L在日期字段中,表示這個月份的最后一天,如一月的31號,非閏年二月的28號;如果L用在星期中,則表示星期六,等同于7。但是,如果L出現(xiàn)在星期字段里,而且在前面有一個數(shù)值X,則表示“這個月的最后X天”,例如,6L表示該月的最后星期五;
- W:該字符只能出現(xiàn)在日期字段里,是對前導日期的修飾,表示離該日期最近的工作日。例如15W表示離該月15號最近的工作日,如果該月15號是星期六,則匹配14號星期五;如果15日是星期日,則匹配16號星期一;如果15號是星期二,那結(jié)果就是15號星期二。但必須注意關(guān)聯(lián)的匹配日期不能夠跨月,如你指定1W,如果1號是星期六,結(jié)果匹配的是3號星期一,而非上個月最后的那天。W字符串只能指定單一日期,而不能指定日期范圍;
- LW組合:在日期字段可以組合使用LW,它的意思是當月的最后一個工作日;
- 井號(#):該字符只能在星期字段中使用,表示當月某個工作日。如6#3表示當月的第三個星期五(6表示星期五,#3表示當前的第三個),而4#5表示當月的第五個星期三,假設(shè)當月沒有第五個星期三,忽略不觸發(fā);
一些例子:
- 0 0 12 * * ?:每天12點運行
- 0 15 10 ? * *:每天10:15運行
- 0 15 10 * * ?:每天10:15運行
- 0 15 10 * * ? *:每天10:15運行
- 0 15 10 * * ? 2008:在2008年的每天10:15運行
- 0 * 14 * * ?:每天14點到15點之間每分鐘運行一次,開始于14:00,結(jié)束于14:59。
- 0 0/5 14 * * ?:每天14點到15點每5分鐘運行一次,開始于14:00,結(jié)束于14:55。
- 0 0/5 14,18 * * ?:每天14點到15點每5分鐘運行一次,此外每天18點到19點每5鐘也運行一次。
- 0 0-5 14 * * ?:每天14:00點到14:05,每分鐘運行一次。
- 0 10,44 14 ? 3 WED:3月每周三的14:10分和14:44執(zhí)行。
- 0 15 10 ? * MON-FRI:每周一,二,三,四,五的10:15分運行。
- 0 15 10 15 * ?:每月15日10:15分運行。
- 0 15 10 L * ?:每月最后一天10:15分運行。
- 0 15 10 ? * 6L:每月最后一個星期五10:15分運行。
- 0 15 10 ? * 6L 2007-2009:在2007,2008,2009年每個月的最后一個星期五的10:15分運行。
- 0 15 10 ? * 6#3:每月第三個星期五的10:15分運行。
2.Scheduler介紹
調(diào)度器scheduler是Quartz中的獨立工作容器,所有的Trigger和Job都需要注冊到scheduler中才能工作。我們可以通過SchedulerFactory來獲取scheduler實例。如下:
//1.獲取默認的標準Scheduler引用
IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler().Result;
//2.通過代碼配置scheduler
NameValueCollection properties = new NameValueCollection
{
//scheduler的名字
["quartz.scheduler.instanceName"] = "MyScheduler",
// 設(shè)置線程池中線程個數(shù)為20個
["quartz.threadPool.threadCount"] = "20",
["quartz.threadPool.type"] = "Quartz.Simpl.SimpleThreadPool, Quartz",
//JobStore類型為內(nèi)存存儲
["quartz.jobStore.type"] = "Quartz.Simpl.RAMJobStore, Quartz"
};
ISchedulerFactory factroy = new StdSchedulerFactory(properties);
IScheduler scheduler= await factroy .GetScheduler();這里列一些會經(jīng)常用的到方法,方法比較簡單直觀,就不一一展示了。
scheduler.PauseJob(JobKey.Create("jobname", "groupname"));//暫停job
scheduler.ResumeJob(JobKey.Create("jobname", "groupname"));//重新啟動job
scheduler.DeleteJob(JobKey.Create("jobname", "groupname"));//刪除job
scheduler.PauseTrigger(new TriggerKey("triggername", "groupname"));//暫停trigger
scheduler.ResumeTrigger(new TriggerKey("triggername", "groupname"));//重新啟動trigger
scheduler.UnscheduleJob(new TriggerKey("triggername", "groupname"));//刪除trigger
scheduler.GetTriggersOfJob(JobKey.Create("jobname", "groupname"));//獲取一個job的所有key
scheduler.Standby(); //暫停所有的觸發(fā)器,可通過shceduler.Start()重啟
scheduler.Shutdown(); //關(guān)閉scheduler,釋放資源。通過Shutdown()關(guān)閉后,不能通過Start()重啟
scheduler.GetMetaData();//獲取scheduler的元數(shù)據(jù)
scheduler.Clear();//清空容器中所有的IJob,ITrigger
//調(diào)度多個任務(wù)
Dictionary> triggersAndJobs = new Dictionary>();
triggersAndJobs.Add(job1, new List() { trigger1,trigger2});
triggersAndJobs.Add(job2, new List() { trigger3});
await scheduler.ScheduleJobs(triggersAndJobs, true);3.Calendar介紹
通過上邊的介紹,我們知道通過觸發(fā)器Trigger可以設(shè)置Job的執(zhí)行時間,但是有時候只使用Trigger來調(diào)度比較麻煩,如一年中每天都執(zhí)行,但是元旦、圣誕節(jié)和情人節(jié)這三天不執(zhí)行。使用trigger也可以實現(xiàn),但是比較麻煩,如果我們有一種方法可以方便地排除這三天就好辦了,Calendar主要作用就是為了排除Trigger中一些特定的時間節(jié)點??匆粋€簡單的栗子:
class Program
{
static void Main(string[] args)
{
//調(diào)度器
IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler().Result;
//JobDetail
IJobDetail job = JobBuilder
.Create()
.Build();
//獲取一個Calendar實例
DailyCalendar calendar = new DailyCalendar(DateBuilder.DateOf(19, 0, 0).DateTime, DateBuilder.DateOf(23, 0, 0).DateTime);//21~23點不執(zhí)行
//將Calendar注冊到Scheduler中
scheduler.AddCalendar("myCalendar", calendar, true, true);//參數(shù)依次是:calendarname,calendar,是否替換同名clendar,是否更新trigger
//獲取一個觸發(fā)器,并將calendar綁定到觸發(fā)器上
ITrigger trigger =
TriggerBuilder.Create()
.StartNow()
.WithIdentity("tname1", "group1")
.WithCronSchedule("0/2 * * * * ?") //2秒執(zhí)行一次
.ModifiedByCalendar("myCalendar") //把Calendar綁定到trigger
.Build();
//start讓調(diào)度線程啟動
scheduler.Start();
//將job添加到調(diào)度器中,同時將trigger綁定到j(luò)ob
scheduler.ScheduleJob(job, trigger).Wait();
Console.ReadKey();
}
}
#region MyJob,繼承IJob接口
///
/// 一個簡單的Job
///
public class MyJob : IJob
{public Task Execute(IJobExecutionContext context)
{
return Task.Run(() =>
{
Console.WriteLine($"觸發(fā)時間:{context.ScheduledFireTimeUtc?.LocalDateTime},下次觸發(fā)時間:{context.NextFireTimeUtc?.LocalDateTime}");
});
}
}
#endregion執(zhí)行結(jié)果:

使用Calendar的流程是:首先獲取一個Calendar實例,然后將Calendar注冊到scheduler容器中,在將Calendar綁定到觸發(fā)器上即可。
Quartz.net中一共提供了六種Calendar,六種Calendar的用法大同小異,列舉如下:
【1】DailyCalendar 用于排除一天中的某一段時間
DailyCalendar calendar = new DailyCalendar(DateBuilder.DateOf(19, 0, 0).DateTime, DateBuilder.DateOf(23, 0, 0).DateTime);//21~23點不執(zhí)行
【2】WeeklyCalendar 用于排除一周中的某幾天
WeeklyCalendar calendar = new WeeklyCalendar(); calendar.SetDayExcluded(DayOfWeek.Sunday, true);//周日不執(zhí)行 //注:如果想讓周日恢復執(zhí)行,執(zhí)行代碼: calendar.SetDayExcluded(DayOfWeek.Sunday, false);
【3】HolidayCalendar 用于排除某些日期
HolidayCalendar calendar = new HolidayCalendar();
calendar.AddExcludedDate(DateTime.Parse("2018/1/2")); //2018年1月2號不執(zhí)行
//注:如果想讓2019/1/9恢復執(zhí)行,執(zhí)行代碼: calendar.RemoveExcludedDate(DateTime.Parse("2018/1/2"));【4】MonthlyCalendar 用于排除每個月的某天*************************************
MonthlyCalendar calendar = new MonthlyCalendar(); calendar.SetDayExcluded(8, true); //每個月的8號不執(zhí)行 //注:如果想讓8號恢復執(zhí)行,執(zhí)行代碼: calendar.SetDayExcluded(8, false);
【5】AnnualCalendar 用于排除一年中的某些天*************************************
AnnualCalendar calendar = new AnnualCalendar();
calendar.SetDayExcluded(DateTime.Parse("2018/1/2"), true);//每年1月2號不執(zhí)行
//注:如果想讓1月8號恢復執(zhí)行,執(zhí)行代碼: calendar.SetDayExcluded(DateTime.Parse("2018/1/2"),true);【6】CronCalendar 用于排除cron表達式表示的時間***************************
CronCalendar calendar = new CronCalendar("* * * 2 1 ?"); //每年的1月2號不執(zhí)行4.Listener介紹
TriggerListener和JobListener用法類似,這里以JobListener為例來介紹Quartz中的Listener。JobListener用于在Job執(zhí)行前、后和被拒絕時執(zhí)行一些動作,和Asp.net中的filter很相似,用法并不復雜,看一個簡單的栗子:
using Quartz;
using Quartz.Impl;
using Quartz.Impl.Matchers;
using System;
using System.Collections.Specialized;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp3
{
internal class Program
{
private static void Main(string[] args)
{
//獲取調(diào)度器
NameValueCollection pairs = new NameValueCollection() { { "quartz.threadPool.ThreadCount", "30" } };
StdSchedulerFactory factory = new StdSchedulerFactory(pairs);
IScheduler scheduler = factory.GetScheduler().Result;
//創(chuàng)建Job
IJobDetail job = JobBuilder
.Create() //獲取JobBuilder
.WithIdentity("jobname1", "group1") //添加Job的名字和分組
.Build(); //生成IJobDetail
//創(chuàng)建rigger
ITrigger trigger = TriggerBuilder.Create() //獲取JobBuilder
.StartAt(DateBuilder.TodayAt(01, 00, 00)) //開始時間,今天的1點(hh,mm,ss),可使用StartNow()
.WithPriority(10) //優(yōu)先級,當觸發(fā)時間一樣時,優(yōu)先級大的觸發(fā)器先執(zhí)行
.WithIdentity("tname1", "group1") //添加名字和分組
.WithSimpleSchedule(x => x.WithIntervalInSeconds(1)
.RepeatForever()
.Build())
.Build();
//啟動調(diào)度器
scheduler.Start();
//myJobListener監(jiān)控所有的job
scheduler.ListenerManager.AddJobListener(new MyJobListener(), GroupMatcher.AnyGroup());
//將job添加到調(diào)度器中,同時將trigger綁定到j(luò)ob
scheduler.ScheduleJob(job, trigger).Wait();
Console.ReadKey();
}
}
#region MyJob,繼承IJob接口
///
/// 一個簡單的Job
///
public class MyJob : IJob
{
public async Task Execute(IJobExecutionContext context)
{
await Task.Run(() =>
{
Console.WriteLine("hello quartz!");
Console.WriteLine($"ThreadPool中的線程個數(shù):{context.Scheduler.GetMetaData().Result.ThreadPoolSize}");
});
}
}
#endregion MyJob,繼承IJob接口
#region myJobListener,繼承IJobListener接口
///
/// 一個簡單的JobListener,triggerListener類似
///
public class MyJobListener : IJobListener
{
public string Name => "hello joblisener";
//job被拒絕時執(zhí)行
public async Task JobExecutionVetoed(IJobExecutionContext context, CancellationToken cancellationToken = default(CancellationToken))
{
await Task.Run(() => { });
}
//job開始前執(zhí)行
public async Task JobToBeExecuted(IJobExecutionContext context, CancellationToken cancellationToken = default(CancellationToken))
{
await Task.Run(() =>
{
Console.WriteLine("myjob-------------begin");
});
}
//job完成后執(zhí)行
public async Task JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException, CancellationToken cancellationToken = default(CancellationToken))
{
await Task.Run(() =>
{
Console.WriteLine("myjob---------------end");
Console.WriteLine();
});
}
#endregion myJobListener,繼承IJobListener接口
}
}上邊例子中,Listener執(zhí)行的動作很簡單,在Job執(zhí)行前打印begin,執(zhí)行后打印end,在實際開發(fā)中,我們可以在通過Listenter來記錄job的執(zhí)行日志,執(zhí)行結(jié)果如下:

五、Quartz.net通過數(shù)據(jù)庫持久化與配置集群。
1.JobStore介紹
學習持久化和集群前我們先了解一下Quartz.net中的JobStore,JobStore用于追蹤任務(wù)調(diào)度相關(guān)的所有數(shù)據(jù),如Job,Trigger,Calendar等。
Quartz.net 提供了兩種JobStore:RAMJobStore,AdoJobStore。
1、RAMJobStore:RAMJobStore是最簡單的JobStore,顧名思義這種JobStore將所有的數(shù)據(jù)都存放在內(nèi)存中,這也是它運行速度快的原因,但是弊端也很明顯:一旦應(yīng)用結(jié)束或者遇到斷電所有的數(shù)據(jù)都會丟失。
RAMJobStore是默認的JobStore,我們也已通過下邊的代碼來顯式設(shè)置使用的JobStore為RAMJobStore。
quartz.jobStore.type = Quartz.Simpl.RAMJobStore, Quartz
2、AdoJobStore:AdoJobStore通過Ado.net將數(shù)據(jù)存儲在數(shù)據(jù)庫中,因此可以解決斷電數(shù)據(jù)丟失的問題,但是因為要讀寫數(shù)據(jù)庫所以效率相對較低。
AdoJobStore官方支持的數(shù)據(jù)庫有:MySql,SqlServer,Sqllite,Oracle等,當前AdoJobStore只有一種類型JobStoreTX,這一點不同于Jave版本,java版本還有JobStoreCMT類型。
2.Db持久化和集群配置
Quartz.net配置數(shù)據(jù)庫持久化和集群比較容易,可以分為簡單的兩步:
第一步:添加數(shù)據(jù)庫表
我們首先要在數(shù)據(jù)庫中添加一系列的表(在Quartz項目的database/tables文件夾下可以找到各種數(shù)據(jù)庫表的生成腳本,Git地址https://github.com/quartznet/quartznet/tree/master/database/tables)。以Sqlserver為例,Sqlserver的數(shù)據(jù)表生成腳本如下:
(略)
在Sqlserver管理器中新建查詢,粘貼上邊的腳本,運行即可生成Quartz的數(shù)據(jù)庫表,數(shù)據(jù)庫的結(jié)構(gòu)如下:

第二步:配置QuartzFactory屬性,直接看代碼:
class Program
{
public static void Main(string[] args)
{
NameValueCollection pars = new NameValueCollection
{
//scheduler名字
["quartz.scheduler.instanceName"] = "MyScheduler",
//線程池個數(shù)
["quartz.threadPool.threadCount"] = "20",
//類型為JobStoreXT,事務(wù)
["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz",
//JobDataMap中的數(shù)據(jù)都是字符串
//["quartz.jobStore.useProperties"] = "true",
//數(shù)據(jù)源名稱
["quartz.jobStore.dataSource"] = "myDS",
//數(shù)據(jù)表名前綴
["quartz.jobStore.tablePrefix"] = "QRTZ_",
//使用Sqlserver的Ado操作代理類
["quartz.jobStore.driverDelegateType"] = "Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz",
//數(shù)據(jù)源連接字符串
["quartz.dataSource.myDS.connectionString"] = "Server=[yourserver];Database=quartzDb;Uid=sa;Pwd=[yourpass]",
//數(shù)據(jù)源的數(shù)據(jù)庫
["quartz.dataSource.myDS.provider"] = "SqlServer",
//序列化類型
["quartz.serializer.type"] = "json",//binary
//自動生成scheduler實例ID,主要為了保證集群中的實例具有唯一標識
["quartz.scheduler.instanceId"] = "AUTO",
//是否配置集群
["quartz.jobStore.clustered"] = "true",
};
StdSchedulerFactory factory = new StdSchedulerFactory(pars);
IScheduler scheduler = factory.GetScheduler().Result;
scheduler.Start();
IJobDetail job = JobBuilder.Create()
.WithIdentity("job1", "g1")
.Build();
ITrigger trigger = TriggerBuilder.Create().WithIdentity("trigger1", "g1").WithCronSchedule("0/1 * * * * ?").Build();
if (scheduler.CheckExists(job.Key).Result)
{
scheduler.DeleteJob(job.Key).Wait();
}
scheduler.ScheduleJob(job, trigger).Wait();
}
}
public class MyJob : IJob
{
public async Task Execute(IJobExecutionContext context)
{
await Task.Run(() =>
{
Console.WriteLine($"hello! 當前時間:{DateTime.Now}");
Console.WriteLine($"觸發(fā)時間:{context.ScheduledFireTimeUtc?.LocalDateTime},下次觸發(fā)時間:{context.NextFireTimeUtc?.LocalDateTime}");
Console.WriteLine();
});
}
}上邊的代碼配置信息在代碼中都有注釋,這個例子實現(xiàn)了一個簡單的任務(wù)調(diào)度:每秒打印一次任務(wù)的觸發(fā)時間和下次觸發(fā)時間。然后運行項目即可,到這里Db持久化和集群都配置完成了。運行程序Quartz會自動在數(shù)據(jù)庫中記錄調(diào)度任務(wù)相關(guān)的數(shù)據(jù)。
Quartz自動向數(shù)據(jù)庫寫入的trigger信息:

Quartz自動向數(shù)據(jù)庫寫入的Job信息:

程序執(zhí)行結(jié)果:

到這里我們看到了Db持久化已經(jīng)實現(xiàn)了,但是上邊的例子,我們在代碼中通過 ["quartz.jobStore.clustered"] = "true" 配置了集群,這個有什么用呢?
首先添加一個debug文件夾的副本

然后運行這兩個文件夾下的xxx.exe文件(如果使用的是.net core,生成的是xxx.dll文件,進入dll文件所在目錄,命令行運行 dotnet xxx.dll 即可啟動),運行結(jié)果如下:

如上所示,運行兩個xxx.exe(core中是dll)后,原文件和副本在同一時間只有一個在運行,所以我們調(diào)度的任務(wù)沒有重復執(zhí)行。如果我們關(guān)掉正在執(zhí)行的那個程序,那么另一個程序會開始執(zhí)行。我們可以得出結(jié)論:Quartz的集群并不會造成任務(wù)重復執(zhí)行,而且當一個服務(wù)器掛了后,另一個服務(wù)器會自動開始執(zhí)行,這種機制大大增加了任務(wù)調(diào)度的容災(zāi)性能。
3.一些需要注意的地方
1.Quartz3.x支持async和await,為提高性能,我們最好將Job中的Execute方法都寫成異步方法;
2.不管使用的是RAMJobStore還是AdoJobStore,千萬不要通過代碼來直接操作JobStore(比如我們直接通過代碼修改數(shù)據(jù)庫中的數(shù)據(jù)),JobStore讓Quartz自動操作即可。
無論使用場景是web應(yīng)用還是桌面程序,我們只使用Scheduler提供的接口方法來實現(xiàn)Job和Trigger等的增/刪/改/查/暫停/恢復即可。
到此這篇關(guān)于作業(yè)調(diào)度框架Quartz.net用法的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
國產(chǎn)化之Arm64?CPU+銀河麒麟系統(tǒng)安裝.NetCore的步驟詳解
這篇文章主要介紹了國產(chǎn)化之Arm64?CPU+銀河麒麟系統(tǒng)安裝.NetCore,這里就以ARM架構(gòu)舉例,其它CPU平臺的安裝過程都一樣,要下載的包不同而已,感興趣的朋友跟隨小編一起看看吧2022-03-03
.NET的DateTime函數(shù)獲取上個月的起始和截止時間的方法
這篇文章主要介紹了NET的DateTime函數(shù)獲取上個月的起始和截止時間的方法,可廣泛使用于報表中的時間自動選擇功能,是非常實用的技巧,需要的朋友可以參考下2015-01-01
如何在ASP.NET Core中使用HttpClientFactory
這篇文章主要介紹了如何在ASP.NET Core中使用HttpClientFactory,幫助大家更好的理解和學習使用.net技術(shù),感興趣的朋友可以了解下2021-04-04
asp.net c#采集需要登錄頁面的實現(xiàn)原理及代碼
當我們采集頁面的時候,如果被采集的網(wǎng)站需要登錄才能采集,原理搞清楚了,就好辦了,我們所要做的僅僅是在采集的時候(或者說HttpWebRequest提交數(shù)據(jù)的時候),將Cookie信息放入Http請求頭里面就可以了,感興趣的朋友可以了解下,或許對你有所幫助2013-02-02
asp.net 長文章通過設(shè)定的行數(shù)分頁
長文章通過設(shè)定的行數(shù)來實現(xiàn)分頁的代碼。2009-12-12

