0. 任務調度
比如說,財務系統需要在每個月初生成上一個月的財務報表。
比如說,每天或每周固定時間對數據庫更新。
比如說,每天定時發送郵件。
這些需要在某個預定的時間點周期性的執行某個特定的任務的功能(也就是任務調度),可以使用任務調度框架——Quartz .NET
Quartz.NET是一個開源的任務調度框架(作業調度框架),是從Java移植過來的,使用較為廣泛!
1. Quartz .NET
1.1 基本概念
調度器(Scheduler):存放觸發器和定時任務,根據觸發器執行定時任務
觸發器(Trigger):決定執行時間,執行間隔,運行次數,故觸發器用來告訴調度程序作業什么時候觸發
任務(Job):需要定時或是周期性執行的任務
使用流程:
創建調度器-->創建任務-->創建觸發器-->Job和Trigger注冊到調度器-->啟動調度器;
1.2 主要接口和對象
接口/類 | 作用 |
---|---|
IScheduler | 調度器接口 |
IJob | 任務接口,將需要定時執行的方法實現在該接口的Excute方法中 |
IJobDetail | 用於定義Job的實例 |
ITrigger | 觸發器接口 |
JobBuilder | 任務構造者:用於創建任務實例 |
TriggerBuilder | 觸發器構造者:用於創建觸發器實例 |
JobDetailImpl | 實現了IJobDetail類 |
JobKey | 任務名 |
TriggerKey | 觸發器名 |
其中觸發器的類型
觸發器最常用的主要有兩種:
SimpleTrigger:用於指定任務重復執行的時間間隔
IMutableTrigger:用於指定任務重復執行的具體時間
2. 使用示例
2.0 准備工作
①安裝Quartz程序包
當前時間:2020年3月18日 23:20:59,最新版本的Quartz.NET為3.0.7
每次的版本的變化,API變化都好大,所以在這里注明當前的使用版本!
建議使用最新版本,新版本都是異步方法實現的。
NuGet:Install-Package Quartz -Version 3.0.7
②新建TestJob.cs
實現IJob接口
public class TestJob : IJob
{
public async Task Execute(IJobExecutionContext context)
{
await Task.Run(() => Console.WriteLine($"{DateTime.Now}:執行任務了……"));
}
}
2.1 每間隔一定時間間隔執行一次任務
//間隔5s重復一次執行指定的任務
public static async void WithInterval()
{
//-------1.准備調度者
ISchedulerFactory factory = new StdSchedulerFactory(); //創建調度器工廠
IScheduler scheduler = await factory.GetScheduler(); //創建調度者
//-------2.准備任務
JobBuilder jobBuilder = JobBuilder.Create<TestJob>();//創建任務構造者:JobBuilder
IJobDetail job1 = jobBuilder.Build();//創建任務
//-------3.准備觸發器
TriggerBuilder triggerBuilder = TriggerBuilder.Create()
.StartNow()//立即生效
.WithSimpleSchedule(x=>x.WithIntervalInSeconds(5)
.RepeatForever()); //創建觸發器構造者:TriggerBuilder
ISimpleTrigger trigger = triggerBuilder.WithIdentity("trigger1","group2").Build() as ISimpleTrigger;//創建觸發器
//-------4.將任務與觸發器添加到調度器中
await scheduler.ScheduleJob(job1, trigger);
await scheduler.Start();//開始執行
}
【代碼說明】
-
示例中使用的而是
ISimpleTrigger
類型的觸發器,可以精准的設置任務重復的時間間隔。 -
其中的觸發器構造者中的
triggerBuilder.StartNow()
表示觸發器立即生效triggerBuilder.StartAt(DateTimeOffset startTimeUtc)
設置觸發器生效的時間triggerBuilder.EndAt(DateTimeOffset startTimeUtc)
設置觸發器失效的時間 -
其中使用
WithIntervalInSeconds(5)
表示每五秒觸發一次任務其他的一些按照小時和天的做間隔,以及明確觸發次數的方法都簡單明確,根據VS的智能提示即可了解,不一一列舉於此!
-
示例中使用
RepeatForever()
表示重復無窮次,還是可以使用WithRepeatCount()
設置重復的次數的。這里有一個細節問題,比如說,設置執行三次,
WithRepeatCount(3)
,但是注意實際會執行4次
2.3 某天的固定時間點執行任務
Quartz.NET的接口比較繁多,第一個示例中是使用的最基礎的方法,下面代碼示例將換一種簡寫的方式。
//每天按照指定的時間點執行任務
public static async void AtHourAndMinute()
{
//創建調度器
IScheduler scheduler = await new StdSchedulerFactory().GetScheduler();
//創建任務
//JobDetailImpl job1 = new JobDetailImpl("TestJob1", "group1", typeo(TestJob))//JobDetailImpl是IJobDetail的實現類
//等價於:
IJobDetail job1 = JobBuilder.Create<TestJob>().WithIdentity("Testjob1""group1").Build();
//創建觸發器
IMutableTrigger trigger2job1 = CronScheduleBuilder.DailyAtHourAndMinut(03, 50).Build();//每天更具某時間點重復觸發任務
//將任務和觸發器添加到調度器中
trigger2job1.Key = new TriggerKey("trigger1");//注意一定要給觸發器命名
await scheduler.ScheduleJob(job1, trigger2job1);
//開始執行調度者
await scheduler.Start();
}
【代碼說明】
-
示例中使用的是
IMutableTrigger
類型的觸發器 -
通過
CronScheduleBuilder
類的靜態方法可以設置觸發的具體的某一日設置觸發時間為每天的某時某分:
DailyAtHourAndMinut(03, 50)
設置觸發時間是一周中的哪幾天中的幾時幾分 :
AtHourAndMinuteOnGivenDaysOfWeek(int hour , int min, params DayOfWeek[] daysOfWeek)
設置觸發時間是每月中某天某時某分 :
CronScheduleBuilder.MonthlyOnDayAndHourAndMinute(int dayOfMonth, int hour, int min).Build()
-
封裝好的一些方法還是有一定局限的(但是我自己夠用的了),關於其他的一些復雜的周期任務,都是可以使用cron expression,使用cron expression可以定義你能想到的所有觸發時間和周期
cron expression什么樣?怎么用?例如設置觸發的時間是:每年每月的2點18分40秒
CronScheduleBuilder.CronSchedule("40 18 2 ? * * *").WithIdentity("trigger1").Build();
關於cron expression寫起來還是有點麻煩的,可以使用一些在線生成器為我們自動的生成期望的表達式。
2.4 封裝整個定時任務,並給任務傳遞參數
前面的示例為了簡潔的表示Quartz.NET的一些API的使用,
項目中都是把為定時任務,整個的操作流程封裝在一個靜態方法中,存放在我們自定義的Job類中
做一個簡單的示例:定時發送短信。
自定義Job,實現IJob接口,同時把創建調度器對象,創建觸發器和任務封裝於其中,作為一個靜態方法
class TestJob2 : IJob
{
public async Task Execute(IJobExecutionContext context)
{
try
{
JobDataMap dataMap = context.MergedJobDataMap;
string tag = dataMap.GetString("tag");
string title = dataMap.GetString("title");
string content = dataMap.GetString("content");
string description = dataMap.GetString("description");
string tels = dataMap.GetString("tels");
//執行定時任務:模擬發送短信
await Task.Run(() => Console.WriteLine($"發短信:【{tag}】,{title}:{content },{description},電話:{tels}。"));
//await context.Scheduler.Shutdown();//表示完成當前的定時任務,關閉調度器
//記入日志
Console.WriteLine("執行了一次定時任務,記入日志");
}
catch (Exception ex)
{
//記入日志Log.Error()
Console.WriteLine(ex.Message);
}
}
//將創建定時任務的所有操作封裝在此
public static async void SendMessage(string starttime, string cronStr,string tag, string title, string content,string description, string tels)
{
try
{
//創建調度器
IScheduler scheduler = await new StdSchedulerFactory().GetScheduler();
//為任務准備參數
DateTime time = DateTime.Parse(starttime);
JobDataMap jobData = new JobDataMap()
{
new KeyValuePair<string, object>("tag", tag),
new KeyValuePair<string, object>("title", title),
new KeyValuePair<string, object>("content", content),
new KeyValuePair<string, object>("description", description),
new KeyValuePair<string, object>("tels", tels),
};
//創建任務:
//注意可以用時間做組名:DateTime.Now.ToLongDateString()
IJobDetail job = JobBuilder.Create<TestJob2>()
.WithIdentity("Testjob1", "group1")
.SetJobData(jobData)
.Build();
//創建觸發器
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity("triger1", "group1")
.StartAt(time)//觸發器開始時間//.StartNow()現在開始
.WithCronSchedule(cronstr)
.Build();
//將任務和觸發器添加到調度器中
await scheduler.ScheduleJob(job, trigger);
await scheduler.Start();
}
catch (Exception ex)
{
//記入日志
Console.WriteLine(ex.Message);
}
}
}
調用:
public static async void PackageJob()
{
//從系統當前時間,每隔5s,發送一條短信:【新聞】,新冠病毒,治愈者越來越多,普天同慶,10086。
await Task.Run(() => TestJob2.SendMessage(DateTime.Now .ToString(),"/5 * * ? * *","新聞", "新冠病毒", "治愈者越來越多", "普天同慶", "10086"));
}
【代碼說明】
-
使用
JobDataMap
類型存放需要傳遞到IJob
接口的Excute(IJobExecutionContext context)
方法中在
JobDataMap
中以鍵值對的方式存放數據,jobDataMao.Add("key",value)
-
在定義Job的時候,使用觸發器對象中的方法
jobBuilder.SetJobData(jobData)
將JobDataMap類型的數據傳遞到任務中 -
使用
JobDataMap dataMap = context.MergedJobDataMap;
獲取傳遞到Excute()中的JobDataMap類型的數據使用
string value = dataMap.GetString("key");
獲取數據 -
因為定時任務的是延時的執行的,所以切記一定要把每個周期中執行的定時任務記入到日志中,便於維護管理!
-
注意,因為實現了IJob接口的任務類,其Excute()方法是在一個單獨的線程中運行的,所以其異常的處理也在Excute()中使用try……catch……進行處理
-
BTW:在MVC項目中使用Quartz .NET,直接在Global.asax.cs中的Application_Start()運行封裝好的定時任務即可
注意:使用Quartz.NET中的Job,是無法實現任何關於Web的相關操作
2.5 關於調度器的一些說明
-
一個調度器中可以調度多個方法
使用
scheduler.ScheduleJob(job,trigger)
將指定的任務和觸發器添加到指定的調度器中,可以多次添加,從而實現一個調度器中調度多個任務但是有一點要注意:一個任務可以有多個觸發器,但是一個觸發器只能對應一個任務
-
調度器可以添加任務,那么就一定是可以移除任務的
//停止觸發器 await scheduler.PauseTrigger(triggerKey); //移除觸發器 await scheduler.UnscheduleJob(triggerKey); //刪除任務 await scheduler.DeleteJob(jobkey);
-
調度器可以開始運行,那么就一定停止運行:
context.Scheduler.Shutdown();
表示完成當前的定時任務,關閉調度器
2.6 關於監聽器
Undone……
可參考監聽器:JobListeners/TriggerListeners/SchedulerListeners
參考及示例代碼下載
-
監聽器:Quartz.NET使用教程
-
Quartz(Java)源碼:Quartz 源碼解析
-
遠程管理及可視化操作: ASP.NET MVC5 實現基於Quartz.NET任務調度
-
配置方式1:Quartz.NET文檔 入門教程
-
配置方式2:Quartz.Net定時任務簡單實用(實例)
-
Cron Expression:Cron Expression Generator
-
封裝任務和傳參:NET作業調度(定時任務)-Quartz.Net