Quartz.Net的關鍵介面
- Scheduler - 與調度程式互動的主要API,[ IScheduler]
- Job - 由希望由調度程式執行的組件實作的介面,[IJob]
- JobDetail - 用于定義作業的實體,[IJobDetail]
- Trigger(即觸發器) - 定義執行給定作業的計劃的組件,[ITrigger]
- JobBuilder - 用于定義/構建JobDetail實體,用于定義作業的實體,
- TriggerBuilder - 用于定義/構建觸發器實體,
public class HelloJob : IJob
{
public Task Execute(IJobExecutionContext context)
{
Console.WriteLine("Hello World");
return Task.CompletedTask;
}
}
class Program
{
static async Task Main(string[] args)
{
StdSchedulerFactory factory = new StdSchedulerFactory();
var scheduler = await factory.GetScheduler();
await scheduler.Start();
// define the job and tie it to our HelloJob class
IJobDetail job = JobBuilder.Create<HelloJob>()
.WithIdentity("myJob", "group1")
.Build();
// Trigger the job to run now, and then every 3 seconds
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity("myTrigger", "group1")
.StartNow()
.WithSimpleSchedule(x => x
.WithIntervalInSeconds(3)
.RepeatForever())
.Build();
await scheduler.ScheduleJob(job, trigger);
// 30秒后停止調度計劃
await Task.Delay(1000 * 30);
await scheduler.Shutdown();
}
}
Scheduler
Scheduler的生命期,從SchedulerFactory創建它時開始,到Scheduler呼叫Shutdown()方法時結束,
Scheduler被創建后,可以增加、洗掉和列舉Job和Trigger,以及執行其它與調度相關的操作(如暫停Trigger),
Scheduler只有在呼叫Start()方法后,才會真正地觸發trigger ,
Job與JobDetail
下面講到的 Job 都是指的是實作 IJob 的類,例如:HelloJob
-
JobDetail
- JobDetail實體是通過JobBuilder類創建的,
// define the job and tie it to our HelloJob class IJobDetail job = JobBuilder.Create<HelloJob>() .WithIdentity("myJob", "group1") .Build();- 注冊到 Scheduler 的不是Job物件,而是 JobDetail 實體 ,Job物件只是 JobDetail 實體的一部分,
await scheduler.ScheduleJob(job, trigger);可以只創建一個job類,然后創建多個與該Job關聯的JobDetail實體,每一個實體都有自己的屬性集和JobDataMap,最后,將所有的實體都加到scheduler中 ,
-
通過JobDetail物件,可以給job實體配置的其它屬性有:
Durability:[StoreDurably()]如果一個job是非持久的,當沒有活躍的trigger與之關聯的時候,會被自動地從scheduler中洗掉,也就是說,非持久的job的生命期是由trigger的存在與否決定的;
RequestsRecovery:[RequestRecovery()]如果一個job是可恢復的,并且在其執行的時候,scheduler發生硬關閉(hard shutdown)(比如運行的行程崩潰了,或者關機了),則當scheduler重新啟動的時候,該job會被重新執行,此時,該job的JobExecutionContext.isRecovering() 回傳true,
-
Job
-
每一個Job都必須實作IJob,例如上面的 HelloJob,這個類僅僅表明該job需要完成什么型別的任務,除此之外,Quartz還需要知道該Job實體所包含的屬性,這將由JobDetail類來完成,
public class HelloJob : IJob { public Task Execute(IJobExecutionContext context) { Console.WriteLine("Hello World"); return Task.CompletedTask; } } -
Job的生命周期
// define the job and tie it to our HelloJob class IJobDetail job = JobBuilder.Create<HelloJob>() .WithIdentity("myJob", "group1") .Build(); // Trigger the job to run now, and then every 40 seconds ITrigger trigger = TriggerBuilder.Create() .WithIdentity("myTrigger", "group1") .StartNow() .WithSimpleSchedule(x => x .WithIntervalInSeconds(3) .RepeatForever()) .Build(); await scheduler.ScheduleJob(job, trigger);可以看到,我們傳給scheduler一個JobDetail實體,因為我們在創建JobDetail時,將要執行的job的類名(HelloJob)傳給了JobDetail,所以scheduler就知道了要執行何種型別的job;
每次當scheduler執行job時,在呼叫其Execute(…)方法之前會創建該類的一個新的實體;執行完畢,對該實體的參考就被丟棄了,實體會被垃圾回收,
Job 的實體要到該執行它們的時候才會實體化出來,每次 Job 被執行,一個新的 Job 實體會被創建,也就是說 Job 不必擔心執行緒安全性,因為同一時刻僅有一個執行緒去執行給定 Job 類的實體,甚至是并發執行同一 Job 也是如此,
這種執行策略需要我們注意: * job必須有一個無參的建構式; * 在job類中,不應該定義有狀態的資料屬性,因為在job的多次執行中,這些屬性的值不會保留,那么如何給job實體增加屬性或配置呢?如何在job的多次執行中,跟蹤job的狀態呢?答案就是:JobDataMap,JobDetail物件的一部分, (后面會詳細介紹用法)
-
Trigger
Trigger物件是用來觸發執行Job的,當調度一個job時,我們實體一個觸發器然后調整它的屬性來滿足job執行的條件,觸發器也有一個和它相關的JobDataMap,它是用來給被觸發器觸發的job傳引數的,
-
SimpleTrigger可以滿足的調度需求是:在具體的時間點執行一次,或者在具體的時間點執行,并且以指定的間隔重復執行若干次,
ITrigger trigger = TriggerBuilder.Create() .WithIdentity("myTrigger", "group1") .StartNow() .WithPriority(5) .WithSimpleSchedule(x => x .WithIntervalInSeconds(3) .RepeatForever()) .Build(); -
CronTrigger基于日歷的概念進行作業啟動計劃,
ITrigger trigger1 = TriggerBuilder.Create() .WithIdentity("myTrigger1", "group1") .ForJob(job) .WithCronSchedule("0 0/3 * * ?") .Build();CronExpression運算式 :https://www.cnblogs.com/yaowen/p/3779284.html
-
優先級(priority) 方法:.WithPriority(5)
如果你的trigger很多(或者Quartz執行緒池的作業執行緒太少),Quartz可能沒有足夠的資源同時觸發所有的trigger;這種情況下,你可能希望控制哪些trigger優先使用Quartz的作業執行緒,要達到該目的,可以在trigger上設定priority屬性,
比如,你有N個trigger需要同時觸發,但只有Z個作業執行緒,優先級最高的Z個trigger會被首先觸發,如果沒有為trigger設定優先級,trigger使用默認優先級,值為5;priority屬性的值可以是任意整數,正數、負數都可以,(只有同時觸發的trigger之間才會比較優先級,10:59觸發的trigger總是在11:00觸發的trigger之前執行,)
Job與Trigger
Trigger對于job而言就好比一個驅動器;沒有觸發器來定時驅動作業,作業就無法運行;
對于Job而言,一個job可以對應多個Trigger,但對于Trigger而言,一個Trigger只能對應一個job;所以一個Trigger 只能被指派給一個 Job;如果你需要一個更復雜的觸發計劃,你可以創建多個 Trigger 并指派它們給同一個 Job,(Trigger實體對應一個JobDetail實體,Job類可以添加到多個JobDetail實體中)
Scheduler 是基于配置在 Job上的 Trigger 來決定正確的執行計劃的,
JobDataMap
重點介紹一下JobDataMap,這是一個非常好用且被我們忽視的屬性,
JobDataMap中可以包含不限量的(序列化的)資料物件,在job實體執行的時候,可以使用其中的資料;JobDataMap是 IDictionary<TKey, TValue> 介面的一個實作,額外增加了一些便于存取基本型別的資料的方法,
JobDetail 和 Trigger 示例都可以設定 JobDataMap(通過UsingJobData()方法)
IJobDetail job = JobBuilder.Create<HelloJob>()
.WithIdentity("myJob", "group1")
.UsingJobData("jobDetail", "J")
.Build();
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity("myTrigger", "group")
.UsingJobData("trigger", "T")
.WithCronSchedule("0/3 * * * * ?")
.Build();
await scheduler.ScheduleJob(job, trigger);
傳遞的值可以通過IJob.Execute(IJobExecutionContext context) 中context.MergedJobDataMap獲取
public class HelloJob : IJob
{
public Task Execute(IJobExecutionContext context)
{
var jobDataMap = context.MergedJobDataMap;
Console.WriteLine($"{DateTime.Now.ToLongTimeString()}-jobDetail:{jobDataMap["jobDetail"]}-trigger:{jobDataMap["trigger"]}");
return Task.CompletedTask;
}
}
執行結果:
11:36:36-jobDetail:J-trigger:T
11:36:39-jobDetail:J-trigger:T
11:36:42-jobDetail:J-trigger:T
...
需要注意的是:
- MergedJobDataMap是將JobDetail.JobDataMap和Trigger.JobDataMap的值合并的,如果key重復,將讀取Trigger中相同可以的值,
- 可以通過 context.JobDetail.JobDataMap 和 context.Trigger.JobDataMap分別讀取,
- 在IJob.Execute()方法中修改任何JobDataMap值,是不會影響到下次Job執行JobDataMap的值的,只在本次Job中有效,
那有什么辦法讓本次執行的狀態修改,影響到以一次執行呢?即修改JobDataMap的值,下一次執行取出的是上一次修改過的?辦法是有的,給Job類打標簽
Job屬性標簽
PersistJobDataAfterExecution:將該注解加在job類上,告訴Quartz在成功執行了job類的execute方法后(沒有發生任何例外),更新JobDetail中JobDataMap的資料,使得該job(即JobDetail)在下一次執行的時候,JobDataMap中是更新后的資料,而不是更新前的舊資料,
[PersistJobDataAfterExecution]
public class HelloJob : IJob
{
public Task Execute(IJobExecutionContext context)
{
var count = context.JobDetail.JobDataMap.GetInt("Count");
Console.WriteLine($"{DateTime.Now.ToLongTimeString()}-Count:{count}");
count++;
context.JobDetail.JobDataMap.Put("Count", count);
return Task.CompletedTask;
}
}
StdSchedulerFactory factory = new StdSchedulerFactory();
var scheduler = await factory.GetScheduler();
await scheduler.Start();
IJobDetail job1 = JobBuilder.Create<HelloJob>()
.WithIdentity("myJob.1", "group")
.UsingJobData("Count", "1")
.Build();
ITrigger trigger1 = TriggerBuilder.Create()
.WithIdentity("myTrigger.1", "group")
.WithCronSchedule("0/3 * * * * ?")
.Build();
await scheduler.ScheduleJob(job1, trigger1);
執行結果:
14:17:09-Count:1
14:17:12-Count:2
14:17:15-Count:3
...
如果使用了[PersistJobDataAfterExecution]標簽,將強烈建議同時使用[DisallowConcurrentExecution]標簽,因為當同一個job(JobDetail)的兩個實體被并發執行時,由于競爭,JobDataMap中存盤的資料很可能是不確定的,
DisallowConcurrentExecution:將該注解加到job類上, 告訴Quartz不要并發地執行同一個job定義的多個實體 ,
舉例說明就是:將 HelloJob 添加到 job1(IJobDetail )
IJobDetail job1 = JobBuilder.Create<HelloJob>()
.WithIdentity("myJob.1", "group")
.UsingJobData("flag", "myJob.1")
.Build();
job1系結觸發器trigger1(ITrigger) 每秒執行一次
ITrigger trigger1 = TriggerBuilder.Create()
.WithIdentity("myTrigger.1", "group")
.WithCronSchedule("0/1 * * * * ?")
.Build();
await scheduler.ScheduleJob(job1, trigger1);
HelloJob 的執行耗時為2秒
public class HelloJob : IJob
{
public Task Execute(IJobExecutionContext context)
{
var flag = context.MergedJobDataMap.GetString("flag");
//模擬耗時2秒
Thread.Sleep(2000);
Console.WriteLine($"{DateTime.Now.ToLongTimeString()}-flag:{flag}");
return Task.CompletedTask;
}
}
首先不打 DisallowConcurrentExecution 標簽,看看輸出結果:
任務啟動:14:39:05
14:39:07-flag:myJob.1
14:39:08-flag:myJob.1
14:39:09-flag:myJob.1
14:39:10-flag:myJob.1
...
通過結果輸入,可以看到,第一次任務是從14:39:05開始,14:39:07結束;第二次的任務接時間是14:39:08,退出開始時間是14:39:06,以此類推...也就說前一個任務未完成,并不影響之后任務的開始
接著我們個 HelloJob 打上 DisallowConcurrentExecution 屬性標簽
[DisallowConcurrentExecution]
public class HelloJob : IJob
{
public Task Execute(IJobExecutionContext context)
{
var flag = context.MergedJobDataMap.GetString("flag");
//模擬耗時2秒
Thread.Sleep(2000);
Console.WriteLine($"{DateTime.Now.ToLongTimeString()}-flag:{flag}");
return Task.CompletedTask;
}
}
看看輸出結果:
任務啟動:14:47:34
14:47:36-flag:myJob.1
14:47:38-flag:myJob.1
14:47:40-flag:myJob.1
14:47:42-flag:myJob.1
...
通過結果輸出可以看到,文本輸出是每兩秒一次,也就說,前一個任務未完成,之后任務不會開始,即不會創建一個新的 HelloJob 實體,這也就不會并發處理任務了,
需要注意的是,DisallowConcurrentExecution 屬性標簽,限制的是 JobDetail ,而不是 Job(HelloJob),同一個JobDetail 實體創建的 Job 不會并發,但,不同的 JobDetail 實體創建的 Job 是可以并發的,
我們再創建一組關于 HelloJob 的任務:job2(IJobDetail),trigger2(ITrigger),HelloJob 不變,
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine($"任務啟動:{DateTime.Now.ToLongTimeString()}");
StdSchedulerFactory factory = new StdSchedulerFactory();
var scheduler = await factory.GetScheduler();
await scheduler.Start();
IJobDetail job1 = JobBuilder.Create<HelloJob>()
.WithIdentity("myJob.1", "group")
.UsingJobData("flag", "myJob.1")
.Build();
ITrigger trigger1 = TriggerBuilder.Create()
.WithIdentity("myTrigger.1", "group")
.WithCronSchedule("0/1 * * * * ?")
.Build();
IJobDetail job2 = JobBuilder.Create<HelloJob>()
.WithIdentity("myJob.2", "group")
.UsingJobData("flag", "myJob.2")
.Build();
ITrigger trigger2 = TriggerBuilder.Create()
.WithIdentity("myTrigger.2", "group")
.WithCronSchedule("0/1 * * * * ?")
.Build();
await scheduler.ScheduleJob(job1, trigger1);
await scheduler.ScheduleJob(job2, trigger2);
Console.ReadKey();
}
}
看看輸出結果:
任務啟動:15:02:24
15:02:26-flag:myJob.1
15:02:26-flag:myJob.2
15:02:28-flag:myJob.1
15:02:28-flag:myJob.2
15:02:30-flag:myJob.1
15:02:30-flag:myJob.2
...
通過結果輸出可以看到,同一個JobDetail,是沒有每秒執行的,即前一個任務沒有完成,后面的任務不會執行,但不同的JobDetail,卻在同一時間執行了,
Job的配置
像上面示例中,我們配置Job,基本都是硬編碼,我們可以把配置移到組態檔中,方便修改和添加
默認組態檔名:quartz_jobs.xml
<?xml version="1.0" encoding="UTF-8"?>
<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>
<job>
<name>myJob.1</name>
<group>group</group>
<description>Hello World!</description>
<job-type>Sayook.Schedule.Client.HelloJob, Sayook.Schedule.Client</job-type>
<job-data-map>
<entry>
<key>flag</key>
<value>myJob.1</value>
</entry>
</job-data-map>
</job>
<trigger>
<cron>
<name>myTrigger.1</name>
<group>group</group>
<description>Hello World! </description>
<job-name>myJob.1</job-name>
<job-group>group</job-group>
<job-data-map>
<entry>
<key>key</key>
<value>1</value>
</entry>
</job-data-map>
<cron-expression>0/3 * * * * ?</cron-expression>
</cron>
</trigger>
</schedule>
</job-scheduling-data>
我們可以將 job_scheduling_data_2_0.xsd 檔案添加到 VisualStudio2019 的XML架構(直接在IDE頂部搜索框搜索xml架構),我們在撰寫xml組態檔的時候就會有提示和驗證了,
class Program
{
static async Task Main(string[] args)
{
var properties = new NameValueCollection
{
["quartz.plugin.xml.type"] = "Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin, Quartz.Plugins",
["quartz.plugin.xml.fileNames"] = "quartz_jobs.xml",
// this is the default
["quartz.plugin.xml.FailOnFileNotFound"] = "true",
// this is not the default
["quartz.plugin.xml.failOnSchedulingError"] = "true"
};
StdSchedulerFactory factory = new StdSchedulerFactory(properties);
var scheduler = await factory.GetScheduler();
await scheduler.Start();
Console.ReadKey();
}
}
["quartz.plugin.xml.FailOnFileNotFound"] = "true",
["quartz.plugin.xml.failOnSchedulingError"] = "true"
上面兩個組態檔強烈建議添加,以為這樣,組態檔錯誤了,會有詳細的例外資訊拋出,以便修改,負責是不會報錯,很難定位問題,
使用組態檔,要參考包:Quartz.Plugins
相關配置可查看文章:Quartz.NET 組態檔詳解 https://www.cnblogs.com/abeam/p/8044460.html
應用示例
基于.NetCore的依賴注入,對Quartz.Net的使用
示例里面包含:
- 可視化面板控制的調度應用[Sayook.Schedule.Manager]
- 使用控制臺應用程式創建的 泛型主機 呼叫應用[Sayook.Schedule.Client]
通過xml檔案配置Job,后續維護、新增Job,對代碼的改動都非常小, 是最輕量級的使用,
示例代碼地址: https://gitee.com/sayook/Sayook.Schedule.Framework
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/24079.html
標籤:.NET Core
下一篇:一、命令列創建專案
