作业调度框架Quartz.NET-现学现用-02-任务监听

前言

任务调度系统并不是完美的,它会出现任务执行失败的情况。如果你需要处理任务失败后的逻辑,希望这篇笔记可以为你提供些帮助。
Quartz.NET的任务监听系统已经被我运用在已上线的工程中,亲测无坑。

Quartz.Listener

要创建一个监听器,只需创建一个实现ITriggerListener或IJobListener接口的对象。然后在运行时向调度程序注册监听器,并且必须为其指定名称(更确切地说,他们必须通过其Name属性来唯一标识自己。)

关键接口和类

  • IJobListener - 与作业相关的事件包括:作业即将执行的通知,以及作业完成执行时的通知。
  • ITriggerListener - 与触发器相关的事件包括:触发器触发,触发错误触发和触发器完成(触发器触发的作业完成)。
  • ListenerManager - 监听器与调度程序的ListenerManager一起注册,并附带一个Matcher,用于描述监听器想要接收事件的作业/触发器。

示例应用程序

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实例
                    NameValueCollection props = new NameValueCollection();
                    StdSchedulerFactory factory = new StdSchedulerFactory(props);
                    IScheduler scheduler = await factory.GetScheduler();
                    //第二步:然后运行它
                    await scheduler.Start();
                    //第三步:定义作业并绑定到HelloJob类,HelloJob类实现IJob接口
                    IJobDetail job = JobBuilder.Create<HelloJob>()
                            .WithIdentity("job1", "group1")
                            //UsingJobData 可以用来传参数
                            .UsingJobData("appKey", "123456QWE")
                            .UsingJobData("appName", "小熊猫")
                            .UsingJobData("api", "https://www.baidu.com")
                            .Build();

                    //第四步:创建触发器。设定,执行一次作业。
                    ITrigger trigger = TriggerBuilder.Create()
                        .WithIdentity("trigger1", "group1") //指定唯一标识,触发器名字,和组名字
                                                            //这对于将作业和触发器组织成“报告作业”和“维护作业”等类别非常有用。
                                                            //作业或触发器的键的名称部分在组内必须是唯一的
                        .StartAt(DateBuilder.FutureDate(5, IntervalUnit.Second)) //可以设定在未来的 5 秒钟后触发    
                        .Build();

                    //第五步:作业与触发器组合,安排任务
                    await scheduler.ScheduleJob(job, trigger);

                    //第六步:创建任务监听,用来解决任务执行失败的情况. HelloJob类实现IJobListener接口
                    IJobListener jobListener = new HelloJob();

                    // 注: 任务监听是通过 IJobListener.Name 来区分的.以下逻辑避免多个任务监听情况下造成的监听被覆盖.
                    // a) 获取当前任务监听实例的名称.
                    var listener = scheduler.ListenerManager.GetJobListener(jobListener.Name);
                    // b) 通过job.Key 获取该任务在调度系统中的唯一实体
                    IMatcher<JobKey> matcher = KeyMatcher<JobKey>.KeyEquals(job.Key);
                    // c) 注意: 任务监听系统中已存在当前任务监听实例,与新添加任务监听的逻辑的区别.
                    if (listener != null)
                    {
                        // 如果已存在该任务监听实例,调用此方法,为该任务监听实例新增监听对象
                        scheduler.ListenerManager.AddJobListenerMatcher(jobListener.Name, matcher);
                    }
                    else
                        // 任务监听系统中不存在该任务监听实例,则调用此方法新增监听对象
                        scheduler.ListenerManager.AddJobListener(jobListener, matcher);

                    //创建触发器监听,触发器监听与任务监听同名也不影响
                    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);

                    //可以设置关闭该调度
                    //await Task.Delay(TimeSpan.FromSeconds(5));
                    //await scheduler.Shutdown();
                }
                catch (SchedulerException se)
                {
                    Console.WriteLine(se);
                }
            }
        }

        //实现IJobListener 接口,实现 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");   //通过键值获取数据
                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}]任务监听,name:{1}|任务执行失败重新执行。"
                    , DateTime.Now.ToLongTimeString(), Name));
                //任务执行失败,再次执行任务
                await Execute(context);
            }

            public async Task JobToBeExecuted(IJobExecutionContext context, CancellationToken cancellationToken = default(CancellationToken))
            {
                await Console.Error.WriteLineAsync(string.Format("[{0}]任务监听,name:{1}|准备执行任务。"
                    , DateTime.Now.ToLongTimeString(), Name));
            }

            public async Task JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException, CancellationToken cancellationToken = default(CancellationToken))
            {
                await Console.Error.WriteLineAsync(string.Format("[{0}]任务监听,name:{1}|任务执行完成。"
                    , 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}]触发器监听,name:{1}|触发器触发成功。"
                    , 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}]触发器监听,name:{1}|触发器开始触发。"
                    , DateTime.Now.ToLongTimeString(), trigger.Key.Name));
            }

            public async Task TriggerMisfired(ITrigger trigger, CancellationToken cancellationToken = default(CancellationToken))
            {
                await Console.Error.WriteLineAsync(string.Format("[{0}]触发器监听,name:{1}|触发器触发失败。"
                    , 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}]触发器监听,name:{1}|可以阻止该任务执行,这里不设阻拦。"
                    , DateTime.Now.ToLongTimeString(), trigger.Key.Name));
                // False 时,不阻止该任务。True 阻止执行
                return false;
            }
            #endregion
        }
    }
}

实验效果

如截图所示,这里只执行一次。注意观察:触发器监听优先级 > 任务监听优先级

image

上篇

上篇:作业调度框架Quartz.NET-01-快速入门

Thanks

Quartz.NET

张善友的博客

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 227,837评论 6 531
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 98,196评论 3 414
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 175,688评论 0 373
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 62,654评论 1 309
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 71,456评论 6 406
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 54,955评论 1 321
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,044评论 3 440
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 42,195评论 0 287
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 48,725评论 1 333
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 40,608评论 3 354
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 42,802评论 1 369
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,318评论 5 358
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,048评论 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,422评论 0 26
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 35,673评论 1 281
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 51,424评论 3 390
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,762评论 2 372