• 【转载】C#作业调度Quartz.NET学习笔记


    C#作业调度Quartz.NET学习笔记

     

        一、简单介绍

        Quartz.NET是一个强大、开源、轻量的作业调度框架,是 OpenSymphony 的 Quartz API 的.NET移植,用C#改写,可用于WinForm和ASP.NET应用中。它灵活而不复杂,可以为执行一个作业而创建简单或复杂的作业调度。它有很多特征,如:数据库支持、集群、插件、支持cron-like表达式等等。

        官网:http://www.quartz-scheduler.net/

        源码:https://github.com/quartznet/quartznet

        示例:http://www.quartz-scheduler.net/documentation/quartz-2.x/quick-start.html

        二、概念解释

        Scheduler:作业调度器。

        IJob:作业接口,继承并实现Execute, 编写执行的具体作业逻辑。

        JobBuilder:根据设置,生成一个详细作业信息(JobDetail)。

        TriggerBuilder:根据规则,生产对应的Trigger。

        三、示例程序

        3.1、界面

        新建一个WinForm程序Client,项目右键->属性->应用程序->输出类型,选择控制台应用程序。

        3.2、引用

        项目右键->管理 NuGet 程序包->Quartz.NET。

        3.2、作业

        新建一个类DataSyncJob并继承IJob,代表它是一个作业,同时实现Execute方法。

    复制代码
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using Quartz;
    
    namespace LinkTo.Test.Quartz.Client
    {
        //打上DisallowConcurrentExecution标签让Job进行单线程跑,避免没跑完时的重复执行。
        [DisallowConcurrentExecution]
        public class DataSyncJob : IJob
        {
            public Task Execute(IJobExecutionContext context)
            {
                JobDataMap keyValuePairs = context.MergedJobDataMap;
                if (keyValuePairs.Count > 0 && keyValuePairs.Contains("Hello"))
                {
                    string value = context.MergedJobDataMap.Get("Hello").ToString();
                    return Task.Run(() =>
                    {
                        Console.WriteLine(DateTime.Now + $" Hello:{value}" + Environment.NewLine);
                    });
                }
                else
                {
                    return Task.Run(() =>
                    {
                        Console.WriteLine(DateTime.Now + Environment.NewLine);
                    });
                }
            }
        }
    }
    复制代码

        说明:

        1)一般来说,作业需打上[DisallowConcurrentExecution]标签,以避免当次作业尚未完成时又被开始调度执行。

        2)作业可以接收触发器传递过来的参数(Key-Value),上面作业接收的是"Hello"参数。

        3.3、调度

    复制代码
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    using Quartz;
    using Quartz.Impl;
    
    namespace LinkTo.Test.Quartz.Client
    {
        public partial class Main : Form
        {
            //调度器工厂
            private ISchedulerFactory factory;
            //调度器
            private IScheduler scheduler;
    
            public Main()
            {
                InitializeComponent();
                //按钮状态
                btnStart.Enabled = true;
                btnStop.Enabled = false;
            }
    
            /// <summary>
            /// 开始
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private async void btnStart_Click(object sender, EventArgs e)
            {
                //1、创建一个调度器
                factory = new StdSchedulerFactory();
                scheduler = await factory.GetScheduler();
                await scheduler.Start();
    
                //2、创建一个任务
                IJobDetail job = JobBuilder.Create<DataSyncJob>().WithIdentity("DataSync", "DataSync_Group").Build();
    
                //3、创建一个触发器(有4种触发器供选择)
                //4、将任务与触发器添加到调度器中
    
                #region 触发器1:WithSimpleSchedule
                //ITrigger simpleTrigger = TriggerBuilder.Create()
                //    .WithIdentity("DataSync_SimpleTrigger", "DataSync_TriggerGroup")
                //    .WithSimpleSchedule(x => x.WithIntervalInSeconds(5).RepeatForever())
                //    .Build();
                //await scheduler.ScheduleJob(job, simpleTrigger);
                #endregion
    
                #region 触发器2:WithDailyTimeIntervalSchedule
                //ITrigger dailyTimeTrigger = TriggerBuilder.Create()
                //    .WithIdentity("DataSync_DailyTimeTrigger", "DataSync_TriggerGroup")
                //    .WithDailyTimeIntervalSchedule(x => x.OnEveryDay().WithIntervalInSeconds(5))
                //    .Build();
                //await scheduler.ScheduleJob(job, dailyTimeTrigger);
                #endregion
    
                #region 触发器3:WithCalendarIntervalSchedule
                //ITrigger calendarTrigger = TriggerBuilder.Create()
                //    .WithIdentity("DataSync_CalendarTrigger", "DataSync_TriggerGroup")
                //    .WithCalendarIntervalSchedule(x => x.WithIntervalInSeconds(5))
                //    .Build();
                //await scheduler.ScheduleJob(job, calendarTrigger);
                #endregion
    
                #region 触发器4:WithCronSchedule(带传递参数"Hello")
                ITrigger cronTrigger = TriggerBuilder.Create()
                    .WithIdentity("DataSync_CronTrigger", "DataSync_TriggerGroup")
                    .WithCronSchedule("0/5 * * * * ?")
                    .UsingJobData("Hello", Guid.NewGuid().ToString())
                    .Build();
                await scheduler.ScheduleJob(job, cronTrigger);
                #endregion
    
                //5、开始执行
                await scheduler.Start();
    
                //按钮状态
                btnStart.Enabled = false;
                btnStop.Enabled = true;
            }
    
            /// <summary>
            /// 停止
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void btnStop_Click(object sender, EventArgs e)
            {
                if (scheduler != null)
                {
                    scheduler.Shutdown(true);
                }
                //按钮状态
                btnStart.Enabled = true;
                btnStop.Enabled = false;
            }
        }
    }
    复制代码

        说明:

        1)Job及Trigger在WithIdentity时都会用到**Group,此处Group的作用是用于分类,相当于一个命名空间。

        2)触发器有4种,分别是WithSimpleSchedule、WithDailyTimeIntervalSchedule、WithCalendarIntervalSchedule、WithCronSchedule,常用第1种及第4种。

        3)WithCronSchedule触发器使用的是Cron表达式。

        3.4、结果

        四、Cron表达式

        1)在线Cron表达式生成器:https://cron.qqe2.com/

        2)官方英文介绍:https://www.quartz-scheduler.net/documentation/quartz-3.x/tutorial/crontrigger.html

        3)Cron表达式整体上还是比较容易理解的,只有一点需要注意"?"号的用法。"?"可以用在 Day of Month 和 Day of Week 中,比如:每月1号的每小时的第31分钟,正确的表达式是:* 31 * 1 * ?,而不能是:* 31 * 1 * *,因为这样代表的是每周的任意一天。

    复制代码
    Cron表达式由7段构成:秒 分 时 日 月 星期 年(可选)
    
    "-":表示范围,MON-WED表示星期一到星期三。
    ",":表示列举,MON,WEB表示星期一和星期三。
    "*":表示"每",每天、每月、每周、每年等。
    "/":表示增量,0/15(分钟)表示每15分钟,在0分以后开始;3/20表示每20分钟,从3分钟以后开始。
    "?":只能出现在日、星期段里面,表示不指定具体的值。
    "L":只能出现在日、星期段里面,是Last的缩写,表示如一个月的最后一天、一个星期的最后一天(星期六)。
    "W":表示工作日,距离给定值最近的工作日。
    "#":表示一个月的第几个星期几,"6#3"表示每个月的第三个星期五(1=SUN...6=FRI,7=SAT)。
    复制代码

        4)官方示例:

    表达式解释
    0 0 12 * * ? 每天中午12点触发
    0 15 10 ? * * 每天上午10:15触发
    0 15 10 * * ? 每天上午10:15触发
    0 15 10 * * ? * 每天上午10:15触发
    0 15 10 * * ? 2005 2005年的每天上午10:15触发
    0 * 14 * * ? 在每天下午2点到下午2:59期间的每1分钟触发
    0 0/5 14 * * ? 在每天下午2点到下午2:55期间的每5分钟触发
    0 0/5 14,18 * * ? 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
    0 0-5 14 * * ? 在每天下午2点到下午2:05期间的每1分钟触发
    0 10,44 14 ? 3 WED 每年三月的星期三的下午2:10和2:44触发
    0 15 10 ? * MON-FRI 周一至周五的上午10:15触发
    0 15 10 15 * ? 每月15日上午10:15触发
    0 15 10 L * ? 每月最后一日的上午10:15触发
    0 15 10 L-2 * ? 每个月的第二天到最后一天的上午10:15触发
    0 15 10 ? * 6L 每月的最后一个星期五上午10:15触发
    0 15 10 ? * 6L 每个月最后一个星期五上午10时15分触发
    0 15 10 ? * 6L 2002-2005 2002年至2005年的每月的最后一个星期五上午10:15触发
    0 15 10 ? * 6#3 每月的第三个星期五上午10:15触发
    0 0 12 1/5 * ? 每月每隔5天下午12点(中午)触发, 从每月的第一天开始
    0 11 11 11 11 ? 每11月11日上午11时11分触发

        五、一行代码实现调度

        新建一个TestJob:

    复制代码
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using Quartz;
    
    namespace LinkTo.Test.Quartz.Client
    {
        //打上DisallowConcurrentExecution标签让Job进行单线程跑,避免没跑完时的重复执行。
        [DisallowConcurrentExecution]
        public class TestJob : IJob
        {
            public Task Execute(IJobExecutionContext context)
            {
                return Task.Run(() =>
                {
                    Console.WriteLine(DateTime.Now + Environment.NewLine);
                });
            }
        }
    }
    复制代码

        新建一个调度封装类QuartzFactory:

    复制代码
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using Quartz;
    using Quartz.Impl;
    using Quartz.Impl.Triggers;
    
    namespace LinkTo.Test.Quartz.Client
    {
        public class QuartzFactory
        {
            //调度器工厂
            private static ISchedulerFactory factory = null;
            //调度器
            private static IScheduler scheduler = null;
    
            /// <summary>
            /// 构造函数
            /// </summary>
            static QuartzFactory()
            {
                factory = new StdSchedulerFactory();
                scheduler = factory.GetScheduler().Result;
                scheduler.Start();
            }
    
            #region 触发器1:添加Job
            /// <summary>
            /// 触发器1:添加Job并以周期的形式运行
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="jobName"></param>
            /// <param name="startTime"></param>
            /// <param name="simpleTime"></param>
            /// <param name="jobDataMap"></param>
            /// <returns></returns>
            public static DateTimeOffset AddJob<T>(string jobName, DateTimeOffset startTime, TimeSpan simpleTime, Dictionary<string, object> jobDataMap) where T : IJob
            {
                IJobDetail jobCheck = JobBuilder.Create<T>().WithIdentity(jobName, jobName + "_Group").Build();
                jobCheck.JobDataMap.PutAll(jobDataMap);
                ISimpleTrigger triggerCheck = new SimpleTriggerImpl(jobName + "_SimpleTrigger", 
                    jobName + "_TriggerGroup",
                    startTime,
                    null,
                    SimpleTriggerImpl.RepeatIndefinitely,
                    simpleTime);
                return scheduler.ScheduleJob(jobCheck, triggerCheck).Result;
            }
    
            /// <summary>
            /// 触发器1:添加Job并以周期的形式运行
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="jobName"></param>
            /// <param name="startTime"></param>
            /// <param name="simpleTime">毫秒数</param>
            /// <param name="mapKey"></param>
            /// <param name="mapValue"></param>
            /// <returns></returns>
            public static DateTimeOffset AddJob<T>(string jobName, DateTimeOffset startTime, int simpleTime, string mapKey, object mapValue) where T : IJob
            {
                Dictionary<string, object> jobDataMap = new Dictionary<string, object>
                {
                    { mapKey, mapValue }
                };
                return AddJob<T>(jobName, startTime, TimeSpan.FromMilliseconds(simpleTime), jobDataMap);
            }
    
            /// <summary>
            /// 触发器1:添加Job并以周期的形式运行
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="jobName"></param>
            /// <param name="startTime"></param>
            /// <param name="simpleTime"></param>
            /// <returns></returns>
            public static DateTimeOffset AddJob<T>(string jobName, DateTimeOffset startTime, TimeSpan simpleTime) where T : IJob
            {
                return AddJob<T>(jobName, startTime, simpleTime, new Dictionary<string, object>());
            }
    
            /// <summary>
            /// 触发器1:添加Job并以周期的形式运行
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="jobName"></param>
            /// <param name="startTime"></param>
            /// <param name="simpleTime">毫秒数</param>
            /// <returns></returns>
            public static DateTimeOffset AddJob<T>(string jobName, DateTimeOffset startTime, int simpleTime) where T : IJob
            {
                return AddJob<T>(jobName, startTime, TimeSpan.FromMilliseconds(simpleTime));
            }
    
            /// <summary>
            /// 触发器1:添加Job并以周期的形式运行
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="jobName"></param>
            /// <param name="simpleTime">毫秒数</param>
            /// <returns></returns>
            public static DateTimeOffset AddJob<T>(string jobName, int simpleTime) where T : IJob
            {
                return AddJob<T>(jobName, DateTime.UtcNow.AddMilliseconds(1), TimeSpan.FromMilliseconds(simpleTime));
            }
            #endregion
    
            #region 触发器4:添加Job
            /// <summary>
            /// 触发器4:添加Job并以定点的形式运行
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="jobName"></param>
            /// <param name="cronTime"></param>
            /// <param name="jobDataMap"></param>
            /// <returns></returns>
            public static DateTimeOffset AddJob<T>(string jobName, string cronTime, string jobData) where T : IJob
            {
                IJobDetail jobCheck = JobBuilder.Create<T>().WithIdentity(jobName, jobName + "_Group").UsingJobData("jobData", jobData).Build();
                ICronTrigger cronTrigger = new CronTriggerImpl(jobName + "_CronTrigger", jobName + "_TriggerGroup", cronTime);
                return scheduler.ScheduleJob(jobCheck, cronTrigger).Result;
            }
    
            /// <summary>
            /// 触发器4:添加Job并以定点的形式运行
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="jobName"></param>
            /// <param name="cronTime"></param>
            /// <returns></returns>
            public static DateTimeOffset AddJob<T>(string jobName, string cronTime) where T : IJob
            {
                return AddJob<T>(jobName, cronTime, null);
            }
            #endregion
    
            /// <summary>
            /// 修改触发器时间重载
            /// </summary>
            /// <param name="jobName">Job名称</param>
            /// <param name="timeSpan">TimeSpan</param>
            /// </summary>
            public static void UpdateTime(string jobName, TimeSpan simpleTimeSpan)
            {
                TriggerKey triggerKey = new TriggerKey(jobName + "_SimpleTrigger", jobName + "_TriggerGroup");
                SimpleTriggerImpl simpleTriggerImpl = scheduler.GetTrigger(triggerKey).Result as SimpleTriggerImpl;
                simpleTriggerImpl.RepeatInterval = simpleTimeSpan;
                scheduler.RescheduleJob(triggerKey, simpleTriggerImpl);
            }
    
            /// <summary>
            /// 修改触发器时间重载
            /// </summary>
            /// <param name="jobName">Job名称</param>
            /// <param name="simpleTime">分钟数</param>
            /// <summary>
            public static void UpdateTime(string jobName, int simpleTime)
            {
                UpdateTime(jobName, TimeSpan.FromMinutes(simpleTime));
            }
    
            /// <summary>
            /// 修改触发器时间重载
            /// </summary>
            /// <param name="jobName">Job名称</param>
            /// <param name="cronTime">Cron表达式</param>
            public static void UpdateTime(string jobName, string cronTime)
            {
                TriggerKey triggerKey = new TriggerKey(jobName + "_CronTrigger", jobName + "_TriggerGroup");
                CronTriggerImpl cronTriggerImpl = scheduler.GetTrigger(triggerKey).Result as CronTriggerImpl;
                cronTriggerImpl.CronExpression = new CronExpression(cronTime);
                scheduler.RescheduleJob(triggerKey, cronTriggerImpl);
            }
    
            /// <summary>
            /// 暂停所有Job
            /// </summary>
            public static void PauseAll()
            {
                scheduler.PauseAll();
            }
    
            /// <summary>
            /// 恢复所有Job
            /// </summary>
            public static void ResumeAll()
            {
                scheduler.ResumeAll();
            }
    
            /// <summary>
            /// 删除指定Job
            /// </summary>
            /// <param name="jobName"></param>
            public static void DeleteJob(string jobName)
            {
                JobKey jobKey = new JobKey(jobName, jobName + "_Group");
                scheduler.DeleteJob(jobKey);
            }
    
            /// <summary>
            /// 卸载定时器
            /// </summary>
            /// <param name="isWaitForToComplete">是否等待Job执行完成</param>
            public static void Shutdown(bool isWaitForToComplete)
            {
                scheduler.Shutdown(isWaitForToComplete);
            }
        }
    }
    复制代码

        一行代码实现作业调度按钮代码:

    复制代码
            /// <summary>
            /// 一行代码实现作业调度
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void btnOneCode_Click(object sender, EventArgs e)
            {
                if (btnOneCode.Text == "一行代码实现作业调度")
                {
                    string cronTime = "0/5 * * * * ?";
                    QuartzFactory.AddJob<TestJob>("TestJob", cronTime);
                    btnOneCode.Text = "Stop";
                }
                else
                {
                    QuartzFactory.DeleteJob("TestJob");
                    btnOneCode.Text = "一行代码实现作业调度";
                }
            }
    复制代码

        参考自:

        https://www.cnblogs.com/best/p/7658573.html

     
  • 相关阅读:
    准确获取URL地址参数
    你不知道的CSS
    axios拦截http拦截
    eclipse无法启动报错、打开Eclipse报错、Eclipse无法打开
    天猫优惠券领取
    微信公众号 订阅号,服务号,企业号区别
    WebService的两种方式SOAP和REST比较 (转)
    线程池
    秒杀应用的MySQL数据库优化
    关系型数据库和非关系型数据库
  • 原文地址:https://www.cnblogs.com/LifeDecidesHappiness/p/15637388.html
Copyright © 2020-2023  润新知