Quartz.NET是一個強大、開源、輕量級的任務調度框架。任務調度在咱們的開發中常常遇到,如說:天天晚上三點讓程序或網站執行某些代碼,或者每隔5秒種執行一個方法等。Windows計劃任務也能實現相似的任務調度,可是Quartz.net有一些有優秀的特性,如:數據庫支持,集羣,插件,支持cron-like表達式等等。 html
官網:http://www.quartz-scheduler.net/git
源碼:https://github.com/quartznet/quartznetgithub
示例:https://www.quartz-scheduler.net/documentation/quartz-3.x/quick-start.html 數據庫
在使用Quart.net前,咱們先理解下quartz的構成和基本工做流程,Quartz包含如下五個基本部分框架
Scheduler 調度器,quartz工做時的獨立容器 Trigger 觸發器,定義了調度任務的時間規則 Job 調度的任務 ThreadPool 線程池(不是clr中的線程池),任務最終交給線程池中的線程執行 JobStore RAWStore和DbStore兩種,job和trigger都存放在JobStore中
Quartz的基本工做流程:scheduler是quartz的獨立運行容器,trigger和job均可以註冊在scheduler容器中,其中trigger是觸發器,用於定義調度任務的時間規則,job是被調度的任務,一個job能夠有多個觸發器,而一個觸發器只能屬於一個job。Quartz中一個調度線程QuartzSchedulerThread,調度線程能夠找到將要被觸發的trigger,經過trigger找到要執行的job,而後在ThreadPool中獲取一個線程來執行這個job。JobStore主要做用是存放job和trigger的信息。異步
用五分鐘看一個簡單的例子吧,這個例子中經過調度器工廠StdSchedulerFactory獲取一個調度器實例scheduler,而後定義Job和trigger,並註冊到調度器中,最後啓動scheduler就能夠執行咱們的任務了。代碼以下:async
class Program { static void Main(string[] args) { //調度器,生成實例的時候線程已經開啓了,不過是在等待狀態 StdSchedulerFactory factory = new StdSchedulerFactory(); IScheduler scheduler = factory.GetScheduler().Result; //建立一個Job,綁定MyJob IJobDetail job = JobBuilder .Create<MyJob>() //獲取JobBuilder .WithIdentity("jobname1", "group1") //添加Job的名字和分組 .WithDescription("一個簡單的任務") //添加描述 .Build(); //生成IJobDetail //建立一個觸發器 ITrigger trigger = TriggerBuilder.Create() //獲取TriggerBuilder .StartAt(DateBuilder.TodayAt(01, 00, 00)) //開始時間,今天的1點(hh,mm,ss),可以使用StartNow() .ForJob(job) //將觸發器關聯給指定的job .WithPriority(10) //優先級,當觸發時間同樣時,優先級大的觸發器先執行 .WithIdentity("tname1", "group1") //添加名字和分組 .WithSimpleSchedule(x => x.WithIntervalInSeconds(1) //調度,一秒執行一次,執行三次 .WithRepeatCount(3) .Build()) .Build(); //start讓調度線程啓動【調度線程能夠從jobstore中獲取快要執行的trigger,而後獲取trigger關聯的job,執行job】 scheduler.Start(); //將job和trigger註冊到scheduler中 scheduler.ScheduleJob(job, trigger).Wait(); Console.ReadKey(); } } #region MyJob /// <summary> /// 一個簡單的Job,全部的Job都要實現IJob接口 /// </summary> 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
代碼中的註釋已經很詳細了,這裏就很少介紹代碼,咱們能夠看到經過Quartz來調度一個任務十分簡單,執行結果以下:網站
在Quartz中Trigger的做用是定義Job什麼時候執行。Quartz.net提供了四種觸發策略:SimpleSchedule,CalendarIntervalSchedule,DailyTimeIntervalSchedule和CronSchedule。TriggerBuilder顧名思義就是用來建立Trigger的。ui
Simpleschedule 是最簡單的一種觸發策略,它的做用相似於timer,能夠設置間隔幾秒/幾分鐘/幾小時執行一次,如建立一秒執行一次的觸發器以下spa
ITrigger trigger = TriggerBuilder.Create() .StartNow() .WithIdentity("tname1", "group1") .WithSimpleSchedule(x => x.WithIntervalInSeconds(1) //設置時間間隔,時分秒 .WithRepeatCount(3)) //設置執行次數,總共執行3+1次, .Build();
CalendarIntervalSchedule擴展了Simplescheduler的功能,Simplescheduler只能在時分秒的維度上指定時間間隔,那麼就有一個問題,若是咱們想一個月執行一次怎麼辦呢?要知道每月的天數是不同的,用SimpleSchedule實現起來就十分麻煩了。CalendarIntervalSchedule能夠實現時分秒天周月年的維度上執行輪詢。如建立一個月執行一次的觸發器以下:這樣
ITrigger trigger = TriggerBuilder.Create() .StartAt(DateTimeOffset.Parse("2018/1/6 13:17:00")) .WithIdentity("tname1", "group1") .WithCalendarIntervalSchedule(x => x.WithIntervalInMonths(1)) //一月執行一次 .Build();
DailyTimeIntervalSchedule主要用於指定每週的某幾天執行,如咱們想讓每週的週六週日的8:00-20:00,每兩秒執行一次,建立觸發器以下:
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點結束 .WithIntervalInSeconds(2) //兩秒執行一次,可設置時分秒維度 .WithRepeatCount(3)) //一共執行3+1次 .Build();
CronSchedule是應用最多的觸發策略,經過Cron表達是咱們能夠輕鬆地表示任意的時間節點,下邊的代碼建立了一個每隔5秒執行一次的觸發器
ITrigger trigger = TriggerBuilder.Create() .StartAt(DateTimeOffset.Parse("2018/1/6 13:17:00")) .WithIdentity("tname1", "group1") .WithCronSchedule("3/5 * * * * ?") //五秒執行一次 .Build();
cron表達式有七個部分組成,以此是秒、分、時、天、月、周、年,其中年是可選的。
位置 | 時間域 | 容許值 | 特殊值 |
---|---|---|---|
1 | 秒 | 0-59 | , - * / |
2 | 分鐘 | 0-59 | , - * / |
3 | 小時 | 0-23 | , - * / |
4 | 日期 | 1-31 | , - * ? / L W C |
5 | 月份 | 1-12 | , - * / |
6 | 星期 | 1-7 | , - * ? / L C # |
7 | 年份(可選) | 1-31 | , - * / |
星號(*):可用在全部字段中,表示對應時間域的每個時刻,例如, 在分鐘字段時,表示「每分鐘」;
問號(?):該字符只在日期和星期字段中使用,它一般指定爲「無心義的值」,至關於點位符;
減號(-):表達一個範圍,如在小時字段中使用「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出如今星期字段裏,並且在前面有一個數值X,則表示「這個月的最後X天」,例如,6L表示該月的最後星期五;
W:該字符只能出如今日期字段裏,是對前導日期的修飾,表示離該日期最近的工做日。例如15W表示離該月15號最近的工做日,若是該月15號是星期六,則匹配14號星期五;若是15日是星期日,則匹配16號星期一;若是15號是星期二,那結果就是15號星期二。但必須注意關聯的匹配日期不可以跨月,如你指定1W,若是1號是星期六,結果匹配的是3號星期一,而非上個月最後的那天。W字符串只能指定單一日期,而不能指定日期範圍;
LW組合:在日期字段能夠組合使用LW,它的意思是當月的最後一個工做日;
井號(#):該字符只能在星期字段中使用,表示當月某個工做日。如6#3表示當月的第三個星期五(6表示星期五,#3表示當前的第三個),而4#5表示當月的第五個星期三,假設當月沒有第五個星期三,忽略不觸發;
Cron表達式對特殊字符的大小寫不敏感,對錶明星期的縮寫英文大小寫也不敏感。
一些例子:
表示式 | 說明 |
---|---|
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,結束於14:59。 |
0 0/5 14 * * ? | 天天14點到15點每5分鐘運行一次,開始於14:00,結束於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執行。 |
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分運行。 |
調度器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", // 設置線程池中線程個數爲20個 ["quartz.threadPool.threadCount"] = "20", ["quartz.threadPool.type"] = "Quartz.Simpl.SimpleThreadPool, Quartz", //JobStore類型爲內存存儲 ["quartz.jobStore.type"] = "Quartz.Simpl.RAMJobStore, Quartz" }; ISchedulerFactory factroy = new StdSchedulerFactory(properties); IScheduler scheduler= await factroy .GetScheduler();
這裏列一些會常常用的到方法,方法比較簡單直觀,就不一一展現了。
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(); //暫停全部的觸發器,可經過shceduler.Start()重啓 scheduler.Shutdown(); //關閉scheduler,釋放資源。經過Shutdown()關閉後,不能經過Start()重啓 scheduler.GetMetaData();//獲取scheduler的元數據 scheduler.Clear();//清空容器中全部的IJob,ITrigger //調度多個任務 Dictionary<IJobDetail, IReadOnlyCollection<ITrigger>> triggersAndJobs = new Dictionary<IJobDetail, IReadOnlyCollection<ITrigger>>(); triggersAndJobs.Add(job1, new List<ITrigger>() { trigger1,trigger2}); triggersAndJobs.Add(job2, new List<ITrigger>() { trigger3}); await scheduler.ScheduleJobs(triggersAndJobs, true);
經過上邊的介紹,咱們知道經過觸發器Trigger能夠設置Job的執行時間,可是有時候只使用Trigger來調度比較麻煩,如一年中天天都執行,可是元旦、聖誕節和情人節這三天不執行。使用trigger也能夠實現,可是比較麻煩,若是咱們有一種方法能夠方便地排除這三天就好辦了,Calendar主要做用就是爲了排除Trigger中一些特定的時間節點。看一個簡單的栗子:
class Program { static void Main(string[] args) { //調度器 IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler().Result; //JobDetail IJobDetail job = JobBuilder .Create<MyJob>() .Build(); //獲取一個Calendar實例 DailyCalendar calendar = new DailyCalendar(DateBuilder.DateOf(19, 0, 0).DateTime, DateBuilder.DateOf(23, 0, 0).DateTime);//21~23點不執行 //將Calendar註冊到Scheduler中 scheduler.AddCalendar("myCalendar", calendar, true, true);//參數依次是:calendarname,calendar,是否替換同名clendar,是否更新trigger //獲取一個觸發器,並將calendar綁定到觸發器上 ITrigger trigger = TriggerBuilder.Create() .StartNow() .WithIdentity("tname1", "group1") .WithCronSchedule("0/2 * * * * ?") //2秒執行一次 .ModifiedByCalendar("myCalendar") //把Calendar綁定到trigger .Build(); //start讓調度線程啓動 scheduler.Start(); //將job添加到調度器中,同時將trigger綁定到job scheduler.ScheduleJob(job, trigger).Wait(); Console.ReadKey(); } } #region MyJob,繼承IJob接口 /// <summary> /// 一個簡單的Job /// </summary> public class MyJob : IJob {public Task Execute(IJobExecutionContext context) { return Task.Run(() => { Console.WriteLine($"觸發時間:{context.ScheduledFireTimeUtc?.LocalDateTime},下次觸發時間:{context.NextFireTimeUtc?.LocalDateTime}"); }); } } #endregion
執行結果:
使用Calendar的流程是:首先獲取一個Calendar實例,而後將Calendar註冊到scheduler容器中,在將Calendar綁定到觸發器上便可。Quartz.net中一共提供了六種Calendar,六種Calendar的用法大同小異,列舉以下:
////【1】.DailyCalendar 用於排除一天中的某一段時間 DailyCalendar calendar = new DailyCalendar(DateBuilder.DateOf(19, 0, 0).DateTime, DateBuilder.DateOf(23, 0, 0).DateTime);//21~23點不執行 ////【2】.WeeklyCalendar 用於排除一週中的某幾天 WeeklyCalendar calendar = new WeeklyCalendar(); calendar.SetDayExcluded(DayOfWeek.Sunday, true);//週日不執行 //注:若是想讓週日恢復執行,執行代碼: calendar.SetDayExcluded(DayOfWeek.Sunday, false); ////【3】.HolidayCalendar 用於排除某些日期 HolidayCalendar calendar = new HolidayCalendar(); calendar.AddExcludedDate(DateTime.Parse("2018/1/2")); //2018年1月2號不執行 //注:若是想讓2019/1/9恢復執行,執行代碼: calendar.RemoveExcludedDate(DateTime.Parse("2018/1/2")); ////【4】.MonthlyCalendar 用於排除每月的某天************************************* MonthlyCalendar calendar = new MonthlyCalendar(); calendar.SetDayExcluded(8,true); //每月的8號不執行 //注:若是想讓8號恢復執行,執行代碼: calendar.SetDayExcluded(8, false); ////【5】AnnualCalendar 用於排除一年中的某些天************************************* AnnualCalendar calendar = new AnnualCalendar(); calendar.SetDayExcluded(DateTime.Parse("2018/1/2"), true);//每年1月2號不執行 //注:若是想讓1月8號恢復執行,執行代碼: calendar.SetDayExcluded(DateTime.Parse("2018/1/2"),true); ////【6】.CronCalendar 用於排除cron表達式表示的時間*************************** CronCalendar calendar = new CronCalendar("* * * 2 1 ?"); //每一年的1月2號不執行
TriggerListener和JobListener用法相似,這裏以JobListener爲例來介紹Quartz中的Listener。JobListener用於在Job執行前、後和被拒絕時執行一些動做,和Asp.net中的filter很類似,用法並不複雜,看一個簡單的栗子:
class Program { static void Main(string[] args) { //獲取調度器 NameValueCollection pairs = new NameValueCollection() { { "quartz.threadPool.ThreadCount", "30" } }; StdSchedulerFactory factory = new StdSchedulerFactory(pairs); IScheduler scheduler = factory.GetScheduler().Result; //建立Job IJobDetail job = JobBuilder .Create<MyJob>() //獲取JobBuilder .WithIdentity("jobname1", "group1") //添加Job的名字和分組 .Build(); //生成IJobDetail //建立rigger ITrigger trigger = TriggerBuilder.Create() //獲取JobBuilder .StartAt(DateBuilder.TodayAt(01, 00, 00)) //開始時間,今天的1點(hh,mm,ss),可以使用StartNow() .WithPriority(10) //優先級,當觸發時間同樣時,優先級大的觸發器先執行 .WithIdentity("tname1", "group1") //添加名字和分組 .WithSimpleSchedule(x => x.WithIntervalInSeconds(1) .RepeatForever() .Build()) .Build(); //啓動調度器 scheduler.Start(); //myJobListener監控全部的job scheduler.ListenerManager.AddJobListener(new MyJobListener(), GroupMatcher<JobKey>.AnyGroup()); //將job添加到調度器中,同時將trigger綁定到job scheduler.ScheduleJob(job, trigger).Wait(); Console.ReadKey(); } } #region MyJob,繼承IJob接口 /// <summary> /// 一個簡單的Job /// </summary> public class MyJob : IJob { public async Task Execute(IJobExecutionContext context) { await Task.Run(() => { Console.WriteLine("hello quartz!"); Console.WriteLine($"ThreadPool中的線程個數:{context.Scheduler.GetMetaData().Result.ThreadPoolSize}");
}); } } #endregion #region myJobListener,繼承IJobListener接口 /// <summary> /// 一個簡單的JobListener,triggerListener相似 /// </summary> public class MyJobListener : IJobListener { public string Name => "hello joblisener"; //job被拒絕時執行 public async Task JobExecutionVetoed(IJobExecutionContext context, CancellationToken cancellationToken = default(CancellationToken)) { await Task.Run(() => { }); } //job開始前執行 public async Task JobToBeExecuted(IJobExecutionContext context, CancellationToken cancellationToken = default(CancellationToken)) { await Task.Run(() => { Console.WriteLine("myjob-------------begin"); }); } //job完成後執行 public async Task JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException, CancellationToken cancellationToken = default(CancellationToken)) { await Task.Run(() => { Console.WriteLine("myjob---------------end");
Console.WriteLine(); }); } #endregion }
上邊例子中,Listener執行的動做很簡單,在Job執行前打印begin,執行後打印end,在實際開發中,咱們能夠在經過Listenter來記錄job的執行日誌,執行結果以下:
這一篇主要介紹了Quartz中的一些基本概念和簡單用法,我的感受Quartz3.x和2.x最大的優點在於:支持異步執行Job,因此建議將Job的Excute方法設計爲異步方法,這樣作能夠提升任務調度和執行效率。