前言
任務(wù)調(diào)度系統(tǒng)并不是完美的,它會出現(xiàn)任務(wù)執(zhí)行失敗的情況。如果你需要處理任務(wù)失敗后的邏輯,希望這篇筆記可以為你提供些幫助。
Quartz.NET的任務(wù)監(jiān)聽系統(tǒng)已經(jīng)被我運(yùn)用在已上線的工程中,親測無坑。
Quartz.Listener
要創(chuàng)建一個監(jiān)聽器,只需創(chuàng)建一個實(shí)現(xiàn)ITriggerListener或IJobListener接口的對象。然后在運(yùn)行時(shí)向調(diào)度程序注冊監(jiān)聽器,并且必須為其指定名稱(更確切地說,他們必須通過其Name屬性來唯一標(biāo)識自己。)
關(guān)鍵接口和類
- IJobListener - 與作業(yè)相關(guān)的事件包括:作業(yè)即將執(zhí)行的通知,以及作業(yè)完成執(zhí)行時(shí)的通知。
- ITriggerListener - 與觸發(fā)器相關(guān)的事件包括:觸發(fā)器觸發(fā),觸發(fā)錯誤觸發(fā)和觸發(fā)器完成(觸發(fā)器觸發(fā)的作業(yè)完成)。
- ListenerManager - 監(jiān)聽器與調(diào)度程序的ListenerManager一起注冊,并附帶一個Matcher,用于描述監(jiān)聽器想要接收事件的作業(yè)/觸發(fā)器。
示例應(yīng)用程序
using Quartz;
using Quartz.Impl;
using Quartz.Impl.Matchers;
using System;
using System.Collections.Specialized;
using System.Threading;
using System.Threading.Tasks;
namespace QuarzLis
{
class Program
{
static void Main(string[] args)
{
StartUpJobs.StartUp().GetAwaiter().GetResult();
Console.ReadKey();
}
public static class StartUpJobs
{
public static async Task StartUp()
{
try
{
//第一步:從工廠中獲取Scheduler實(shí)例
NameValueCollection props = new NameValueCollection();
StdSchedulerFactory factory = new StdSchedulerFactory(props);
IScheduler scheduler = await factory.GetScheduler();
//第二步:然后運(yùn)行它
await scheduler.Start();
//第三步:定義作業(yè)并綁定到HelloJob類,HelloJob類實(shí)現(xiàn)IJob接口
IJobDetail job = JobBuilder.Create<HelloJob>()
.WithIdentity("job1", "group1")
//UsingJobData 可以用來傳參數(shù)
.UsingJobData("appKey", "123456QWE")
.UsingJobData("appName", "小熊貓")
.UsingJobData("api", "https://www.baidu.com")
.Build();
//第四步:創(chuàng)建觸發(fā)器。設(shè)定,執(zhí)行一次作業(yè)。
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity("trigger1", "group1") //指定唯一標(biāo)識,觸發(fā)器名字,和組名字
//這對于將作業(yè)和觸發(fā)器組織成“報(bào)告作業(yè)”和“維護(hù)作業(yè)”等類別非常有用。
//作業(yè)或觸發(fā)器的鍵的名稱部分在組內(nèi)必須是唯一的
.StartAt(DateBuilder.FutureDate(5, IntervalUnit.Second)) //可以設(shè)定在未來的 5 秒鐘后觸發(fā)
.Build();
//第五步:作業(yè)與觸發(fā)器組合,安排任務(wù)
await scheduler.ScheduleJob(job, trigger);
//第六步:創(chuàng)建任務(wù)監(jiān)聽,用來解決任務(wù)執(zhí)行失敗的情況. HelloJob類實(shí)現(xiàn)IJobListener接口
IJobListener jobListener = new HelloJob();
// 注: 任務(wù)監(jiān)聽是通過 IJobListener.Name 來區(qū)分的.以下邏輯避免多個任務(wù)監(jiān)聽情況下造成的監(jiān)聽被覆蓋.
// a) 獲取當(dāng)前任務(wù)監(jiān)聽實(shí)例的名稱.
var listener = scheduler.ListenerManager.GetJobListener(jobListener.Name);
// b) 通過job.Key 獲取該任務(wù)在調(diào)度系統(tǒng)中的唯一實(shí)體
IMatcher<JobKey> matcher = KeyMatcher<JobKey>.KeyEquals(job.Key);
// c) 注意: 任務(wù)監(jiān)聽系統(tǒng)中已存在當(dāng)前任務(wù)監(jiān)聽實(shí)例,與新添加任務(wù)監(jiān)聽的邏輯的區(qū)別.
if (listener != null)
{
// 如果已存在該任務(wù)監(jiān)聽實(shí)例,調(diào)用此方法,為該任務(wù)監(jiān)聽實(shí)例新增監(jiān)聽對象
scheduler.ListenerManager.AddJobListenerMatcher(jobListener.Name, matcher);
}
else
// 任務(wù)監(jiān)聽系統(tǒng)中不存在該任務(wù)監(jiān)聽實(shí)例,則調(diào)用此方法新增監(jiān)聽對象
scheduler.ListenerManager.AddJobListener(jobListener, matcher);
//創(chuàng)建觸發(fā)器監(jiān)聽,觸發(fā)器監(jiān)聽與任務(wù)監(jiān)聽同名也不影響
ITriggerListener triggerListener = new HelloJob();
var triListener = scheduler.ListenerManager.GetTriggerListener(triggerListener.Name);
IMatcher<TriggerKey> triMatcher = KeyMatcher<TriggerKey>.KeyEquals(trigger.Key);
if (triListener != null)
{
scheduler.ListenerManager.AddTriggerListenerMatcher(triggerListener.Name, triMatcher);
}
else
scheduler.ListenerManager.AddTriggerListener(triggerListener, triMatcher);
//可以設(shè)置關(guān)閉該調(diào)度
//await Task.Delay(TimeSpan.FromSeconds(5));
//await scheduler.Shutdown();
}
catch (SchedulerException se)
{
Console.WriteLine(se);
}
}
}
//實(shí)現(xiàn)IJobListener 接口,實(shí)現(xiàn) ITriggerListener 接口,這里和 IJob邏輯放在了一起
public class HelloJob : IJob, IJobListener, ITriggerListener
{
private string appKey;
private string appName;
private string appApi;
public string Name
{
get;
}
public HelloJob()
{
this.Name = this.GetType().ToString();
}
public HelloJob(string name)
{
this.Name = name;
}
public async Task Execute(IJobExecutionContext context)
{
JobKey jkey = context.JobDetail.Key;
TriggerKey tKey = context.Trigger.Key;
JobDataMap dataMap = context.MergedJobDataMap;
appKey = dataMap.GetString("appKey"); //通過鍵值獲取數(shù)據(jù)
appName = dataMap.GetString("appName");
appApi = dataMap.GetString("api");
await Console.Error.WriteLineAsync(
string.Format("[{0}]開始推送:\nJobKey:{1}\nTriggerKey:{2}\nAppKey:{3} appName: {4} , and AppAPI: {5}"
, DateTime.Now.ToLongTimeString(), jkey, tKey, appKey, appName, appApi));
}
#region IJobListener
public async Task JobExecutionVetoed(IJobExecutionContext context, CancellationToken cancellationToken = default(CancellationToken))
{
await Console.Error.WriteLineAsync(string.Format("[{0}]任務(wù)監(jiān)聽,name:{1}|任務(wù)執(zhí)行失敗重新執(zhí)行。"
, DateTime.Now.ToLongTimeString(), Name));
//任務(wù)執(zhí)行失敗,再次執(zhí)行任務(wù)
await Execute(context);
}
public async Task JobToBeExecuted(IJobExecutionContext context, CancellationToken cancellationToken = default(CancellationToken))
{
await Console.Error.WriteLineAsync(string.Format("[{0}]任務(wù)監(jiān)聽,name:{1}|準(zhǔn)備執(zhí)行任務(wù)。"
, DateTime.Now.ToLongTimeString(), Name));
}
public async Task JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException, CancellationToken cancellationToken = default(CancellationToken))
{
await Console.Error.WriteLineAsync(string.Format("[{0}]任務(wù)監(jiān)聽,name:{1}|任務(wù)執(zhí)行完成。"
, DateTime.Now.ToLongTimeString(), Name));
}
#endregion
#region ITriggerListener
public async Task TriggerComplete(ITrigger trigger, IJobExecutionContext context, SchedulerInstruction triggerInstructionCode, CancellationToken cancellationToken = default(CancellationToken))
{
await Console.Error.WriteLineAsync(string.Format("[{0}]觸發(fā)器監(jiān)聽,name:{1}|觸發(fā)器觸發(fā)成功。"
, DateTime.Now.ToLongTimeString(), trigger.Key.Name));
}
public async Task TriggerFired(ITrigger trigger, IJobExecutionContext context, CancellationToken cancellationToken = default(CancellationToken))
{
await Console.Error.WriteLineAsync(string.Format("[{0}]觸發(fā)器監(jiān)聽,name:{1}|觸發(fā)器開始觸發(fā)。"
, DateTime.Now.ToLongTimeString(), trigger.Key.Name));
}
public async Task TriggerMisfired(ITrigger trigger, CancellationToken cancellationToken = default(CancellationToken))
{
await Console.Error.WriteLineAsync(string.Format("[{0}]觸發(fā)器監(jiān)聽,name:{1}|觸發(fā)器觸發(fā)失敗。"
, DateTime.Now.ToLongTimeString(), trigger.Key.Name));
}
public async Task<bool> VetoJobExecution(ITrigger trigger, IJobExecutionContext context, CancellationToken cancellationToken = default(CancellationToken))
{
await Console.Error.WriteLineAsync(string.Format("[{0}]觸發(fā)器監(jiān)聽,name:{1}|可以阻止該任務(wù)執(zhí)行,這里不設(shè)阻攔。"
, DateTime.Now.ToLongTimeString(), trigger.Key.Name));
// False 時(shí),不阻止該任務(wù)。True 阻止執(zhí)行
return false;
}
#endregion
}
}
}
實(shí)驗(yàn)效果
如截圖所示,這里只執(zhí)行一次。注意觀察:觸發(fā)器監(jiān)聽優(yōu)先級 > 任務(wù)監(jiān)聽優(yōu)先級

image
上篇
上篇:作業(yè)調(diào)度框架Quartz.NET-01-快速入門