.NET6+Quartz实现定时任务

  • .NET6+Quartz实现定时任务已关闭评论
  • 118 次浏览
  • A+
所属分类:.NET技术
摘要

在实际工作中,经常会有一些需要定时操作的业务,如:定时发邮件,定时统计信息等内容,那么如何实现才能使得我们的项目整齐划一呢?本文通过一些简单的小例子,简述在.Net6+Quartz实现定时任务的一些基本操作,及相关知识介绍,仅供学习分享使用,如有不足之处,还请指正。

在实际工作中,经常会有一些需要定时操作的业务,如:定时发邮件,定时统计信息等内容,那么如何实现才能使得我们的项目整齐划一呢?本文通过一些简单的小例子,简述在.Net6+Quartz实现定时任务的一些基本操作,及相关知识介绍,仅供学习分享使用,如有不足之处,还请指正。

什么是定时任务?

定时任务,也叫任务调度,是指在一定的载体上,根据具体的触发规则,执行某些操作。所以定时任务需要满足三个条件:载体(Scheduler),触发规则(Trigger),具体业务操作(Job)。如下所示:

.NET6+Quartz实现定时任务

什么是Quartz?

Quartz 是一个开源的作业调度框架,它完全由 Java 写成,并设计用于 J2SE 和 J2EE 应用中。它提供了巨大的灵 活性而不牺牲简单性。你能够用它来为执行一个作业而创建简单的或复杂的调度。它有很多特征,如:数据库支持,集群,插件,EJB 作业预构 建,JavaMail 及其它,支持 cron-like 表达式等等。虽然Quartz最初是为Java编写的,但是目前已经有.Net版本的Quartz,所以在.Net中应用Quartz已经不再是奢望,而是轻而易举的事情了。

Github上开源网址为:https://github.com/quartznet

.NET6+Quartz实现定时任务

 

关于Quartz的快速入门和API文档,可以参考:https://www.quartz-scheduler.net/documentation/quartz-3.x/quick-start.html

 

涉及知识点

在Quartz框架中,主要接口和API如下所示:

.NET6+Quartz实现定时任务

 

 其中IScheduler,ITrigger , IJob 三者之间的关系,如下所示:

.NET6+Quartz实现定时任务

 

 

 Quartz安装

为了方便,本示例创建一个基于.Net6.0的控制台应用程序,在VS2022中,通过Nuget包管理器进行安装,如下所示:

.NET6+Quartz实现定时任务

 

创建一个简单的定时器任务

要开发一个简单,完整且能运行的定时器任务,步骤如下所示:

1. 创建工作单元Job

创建任务需要实现IJob接口,如下所示:

 1 using Quartz;  2 using System.Diagnostics;  3   4 namespace DemoQuartz.QuartzA.Job  5 {  6     /// <summary>  7     /// 测试任务,实现IJob接口  8     /// </summary>  9     public class TestJob : IJob 10     { 11         public TestJob() 12         { 13             Console.WriteLine("执行构造函数");//表示每一次计划执行,都是一次新的实例 14         } 15  16         public Task Execute(IJobExecutionContext context) 17         { 18             return Task.Run(() => 19              { 20                  Console.WriteLine($"******************************"); 21                  Console.WriteLine($"测试信息{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}"); 22                  Console.WriteLine($"******************************"); 23                  Console.WriteLine(); 24              }); 25         } 26     } 27 }

2. 创建时间轴Scheduler

时间轴也是任务执行的载体,可以通过StdSchedulerFactory进行获取,如下所示:

1 //创建计划单元(时间轴,载体) 2 StdSchedulerFactory schedulerFactory = new StdSchedulerFactory(); 3 var scheduler = await schedulerFactory.GetScheduler(); 4 await scheduler.Start();

3. 创建触发规则Trigger

触发规则就是那些时间点执行任务,可通过TriggerBuilder进行构建,如下所示:

1 //Trigger时间触发机制 2 var trigger = TriggerBuilder.Create() 3     .WithIdentity("TestTrigger","TestGroup") 4     //.StartNow() //立即执行 5     .WithSimpleSchedule(w=>w.WithIntervalInSeconds(5).WithRepeatCount(5))//.RepeatForever()//无限循环 6     //.WithCronSchedule("5/10 * * * * ?") //通过Cron表达式定制时间触发规则, 示例表示从5开始,每隔10秒一次 7     .Build();

4. 创建任务描述

任务描述定义了具体的任务名称,分组等内容。可通过JobBuilder进行构建,如下所示:

1 //Job详细描述 2 var jobDetail = JobBuilder.Create<TestJob>() 3     .WithDescription("这是一个测试Job") 4     .WithIdentity("TestJob", "TestGroup") 5     .Build();

5. 建立三者联系

通过载体,将规则和工作单元串联起来,如下所示:

1 //把时间和任务通过载体关联起来 2 await scheduler.ScheduleJob(jobDetail, trigger);

6. 简单示例测试

通过运行程序,示例结果如下所示:

.NET6+Quartz实现定时任务

 

传递参数

在Quartz框架下,如果需要给执行的Job传递参数,可以通过两种方式:

jobDetail.JobDataMap,工作描述时通过JobDataMap传递参数。

trigger.JobDataMap, 时间触发时通过JobDataMap传递参数。

在Job工作单元中,可以通过Context中对应的JobDataMap获取参数。

传递参数,如下所示:

 1 //传递参数  2 jobDetail.JobDataMap.Add("name", "Alan");  3 jobDetail.JobDataMap.Add("age", 20);  4 jobDetail.JobDataMap.Add("sex", true);  5   6   7 //trigger同样可以传递参数  8 trigger.JobDataMap.Add("like1", "meimei");  9 trigger.JobDataMap.Add("like2", "football"); 10 trigger.JobDataMap.Add("like3", "sing");

获取参数,如下所示:

 1 //获取参数  2 var name = context.JobDetail.JobDataMap.GetString("name");  3 var age = context.JobDetail.JobDataMap.GetInt("age");  4 var sex = context.JobDetail.JobDataMap.GetBoolean("sex") ? "" : "";  5   6 var like1 = context.Trigger.JobDataMap.GetString("like1");  7 var like2 = context.Trigger.JobDataMap.GetString("like2");  8 var like3 = context.Trigger.JobDataMap.GetString("like3");  9  10 //context.MergedJobDataMap.GetString("aa");//注意如果使用MergedJobDataMap,JobDetail和Trigger中用到相同的Key,则后面设置的会覆盖前面设置的。

注意:如果使用MergedJobDataMap,JobDetail和Trigger中用到相同的Key,则后面设置的会覆盖前面设置的。

任务特性

假如我们的定时任务,执行一次需要耗时比较久,而且后一次执行需要等待前一次完成,并且需要前一次执行的结果作为参考,那么就需要设置任务的任性。因为默认情况下,工作单元在每一次运行都是一个新的实例,相互之间独立运行,互不干扰。所以如果需要存在一定的关联,就要设置任务的特性,主要有两个,如下所示:

  • [PersistJobDataAfterExecution]//在执行完成后,保留JobDataMap数据
  • [DisallowConcurrentExecution]//不允许并发执行,即必须等待上次完成后才能执行下一次

 

 

 以上两个特性,只需要标记在任务对应的类上即可。标记上后,只需要往对应的JobDataMap中添加值即可。

监听器

在Quartz框架下,有三种监听器,分别是:时间轴监听器ISchedulerListener,触发规则监听器ITriggerListener,任务监听器IJobListener。要实现对应监听器,实现对应接口即可。实现监听器步骤:

1. 创建监听器

根据不同的需要,可以创建不同的监听器,如下所示:

时间轴监听器SchedulerListener

  1 public class TestSchedulerListener : ISchedulerListener   2 {   3     public Task JobAdded(IJobDetail jobDetail, CancellationToken cancellationToken = default)   4     {   5         return Task.Run(() => {   6             Console.WriteLine("Test Job is added.");   7         });   8     }   9   10     public Task JobDeleted(JobKey jobKey, CancellationToken cancellationToken = default)  11     {  12         return Task.Run(() => {  13             Console.WriteLine("Test Job is deleted.");  14         });  15     }  16   17     public Task JobInterrupted(JobKey jobKey, CancellationToken cancellationToken = default)  18     {  19         return Task.Run(() => {  20             Console.WriteLine("Test Job is Interrupted.");  21         });  22     }  23   24     public Task JobPaused(JobKey jobKey, CancellationToken cancellationToken = default)  25     {  26         return Task.Run(() => {  27             Console.WriteLine("Test Job is paused.");  28         });  29     }  30   31     public Task JobResumed(JobKey jobKey, CancellationToken cancellationToken = default)  32     {  33         return Task.Run(() => {  34             Console.WriteLine("Test Job is resumed.");  35         });  36     }  37   38     public Task JobScheduled(ITrigger trigger, CancellationToken cancellationToken = default)  39     {  40         return Task.Run(() => {  41             Console.WriteLine("Test Job is scheduled.");  42         });  43     }  44   45     public Task JobsPaused(string jobGroup, CancellationToken cancellationToken = default)  46     {  47         return Task.Run(() => {  48             Console.WriteLine("Test Jobs is paused.");  49         });  50     }  51   52     public Task JobsResumed(string jobGroup, CancellationToken cancellationToken = default)  53     {  54         return Task.Run(() => {  55             Console.WriteLine("Test Jobs is resumed.");  56         });  57     }  58   59     public Task JobUnscheduled(TriggerKey triggerKey, CancellationToken cancellationToken = default)  60     {  61         return Task.Run(() => {  62             Console.WriteLine("Test Jobs is un schedulered.");  63         });  64     }  65   66     public Task SchedulerError(string msg, SchedulerException cause, CancellationToken cancellationToken = default)  67     {  68         return Task.Run(() => {  69             Console.WriteLine("Test scheduler is error.");  70         });  71     }  72   73     public Task SchedulerInStandbyMode(CancellationToken cancellationToken = default)  74     {  75         return Task.Run(() => {  76             Console.WriteLine("Test scheduler is standby mode.");  77         });  78     }  79   80     public Task SchedulerShutdown(CancellationToken cancellationToken = default)  81     {  82         return Task.Run(() => {  83             Console.WriteLine("Test scheduler is shut down.");  84         });  85     }  86   87     public Task SchedulerShuttingdown(CancellationToken cancellationToken = default)  88     {  89         return Task.Run(() => {  90             Console.WriteLine("Test scheduler is shutting down.");  91         });  92     }  93   94     public Task SchedulerStarted(CancellationToken cancellationToken = default)  95     {  96         return Task.Run(() => {  97             Console.WriteLine("Test scheduleer is started.");  98         });  99     } 100  101     public Task SchedulerStarting(CancellationToken cancellationToken = default) 102     { 103         return Task.Run(() => { 104             Console.WriteLine("Test scheduler is starting."); 105         }); 106     } 107  108     public Task SchedulingDataCleared(CancellationToken cancellationToken = default) 109     { 110         return Task.Run(() => { 111             Console.WriteLine("Test scheduling is cleared."); 112         }); 113     } 114  115     public Task TriggerFinalized(ITrigger trigger, CancellationToken cancellationToken = default) 116     { 117         return Task.Run(() => { 118             Console.WriteLine("Test trigger is finalized."); 119         }); 120     } 121  122     public Task TriggerPaused(TriggerKey triggerKey, CancellationToken cancellationToken = default) 123     { 124         return Task.Run(() => { 125             Console.WriteLine("Test trigger is paused."); 126         }); 127     } 128  129     public Task TriggerResumed(TriggerKey triggerKey, CancellationToken cancellationToken = default) 130     { 131         return Task.Run(() => { 132             Console.WriteLine("Test trigger is resumed."); 133         }); 134     } 135  136     public Task TriggersPaused(string? triggerGroup, CancellationToken cancellationToken = default) 137     { 138         return Task.Run(() => { 139             Console.WriteLine("Test triggers is paused."); 140         }); 141     } 142  143     public Task TriggersResumed(string? triggerGroup, CancellationToken cancellationToken = default) 144     { 145         return Task.Run(() => { 146             Console.WriteLine("Test triggers is resumed."); 147         }); 148     } 149 }

触发规则监听器TriggerListener

 1 /// <summary>  2 /// 触发器监听  3 /// </summary>  4 public class TestTriggerListener : ITriggerListener  5 {  6     public string Name => "TestTriggerListener";  7   8     public Task TriggerComplete(ITrigger trigger, IJobExecutionContext context, SchedulerInstruction triggerInstructionCode, CancellationToken cancellationToken = default)  9     { 10         //任务完成 11         return Task.Run(() => { 12             Console.WriteLine("Test trigger is complete."); 13          14         }); 15     } 16  17     public Task TriggerFired(ITrigger trigger, IJobExecutionContext context, CancellationToken cancellationToken = default) 18     { 19         return Task.Run(() => { 20             Console.WriteLine("Test trigger is fired."); 21  22         }); 23     } 24  25     public Task TriggerMisfired(ITrigger trigger, CancellationToken cancellationToken = default) 26     { 27         return Task.Run(() => { 28             Console.WriteLine("Test trigger is misfired."); 29  30         }); 31     } 32  33     public Task<bool> VetoJobExecution(ITrigger trigger, IJobExecutionContext context, CancellationToken cancellationToken = default) 34     { 35         return Task.Run(() => { 36             Console.WriteLine("Test trigger is veto."); 37             return false;//是否终止 38         }); 39     } 40 }

JobListener任务监听器

 1 /// <summary>  2 /// TestJob监听器  3 /// </summary>  4 public class TestJobListener : IJobListener  5 {  6     public string Name => "TestJobListener";  7   8     public Task JobExecutionVetoed(IJobExecutionContext context, CancellationToken cancellationToken = default)  9     { 10         //任务被终止时 11         return Task.Run(() => { 12             Console.WriteLine("Test Job is vetoed."); 13         }); 14     } 15  16     public Task JobToBeExecuted(IJobExecutionContext context, CancellationToken cancellationToken = default) 17     { 18         //任务被执行时 19         return Task.Run(() => { 20             Console.WriteLine("Test Job is to be executed."); 21         }); 22     } 23  24     public Task JobWasExecuted(IJobExecutionContext context, JobExecutionException? jobException, CancellationToken cancellationToken = default) 25     { 26         //任务已经执行 27         return Task.Run(() => { 28             Console.WriteLine("Test Job was executed."); 29         }); 30     } 31 }

2. 添加监听

在时间轴上的监听管理器中进行添加,如下所示:

1 //增加监听 2 scheduler.ListenerManager.AddJobListener(new TestJobListener()); 3 scheduler.ListenerManager.AddTriggerListener(new TestTriggerListener()); 4 scheduler.ListenerManager.AddSchedulerListener(new TestSchedulerListener());

日志管理

在Quartz框架中,创建之前会进行日志创建检测,所以如果需要获取框架中的日志信息,可以进行创建实现ILogProvider,如下所示:

 1 public class TestLogProvider : ILogProvider  2 {  3     public Logger GetLogger(string name)  4     {  5         return (level, func, exception, parameters) =>  6         {  7             if (level >= Quartz.Logging.LogLevel.Info && func != null)  8             {  9                 Console.WriteLine("[" + DateTime.Now.ToLongTimeString() + "] [" + level + "] " + func(), parameters); 10             } 11             return true; 12         }; 13     } 14  15     public IDisposable OpenMappedContext(string key, object value, bool destructure = false) 16     { 17         throw new NotImplementedException(); 18     } 19  20     public IDisposable OpenNestedContext(string message) 21     { 22         throw new NotImplementedException(); 23     } 24 }

然后在当前的Scheduler中,添加日志即可,如下所示:

1 //日志 2 LogProvider.SetCurrentLogProvider(new TestLogProvider());

完整示例

在添加了监听器,日志,参数传递,任务特性后,完整的目录结构,如下所示:

.NET6+Quartz实现定时任务

 

 示例截图

.NET6+Quartz实现定时任务

 

以上就是.Net6.0+Quartz开发控制台定时任务调度的全部内容。以上任务都是硬编码的固定程序,包括任务的启停,那么如果能通过可视化界面来创建以及管理任务,是不是一件很爽的事情呢,这也是后续需要探讨的内容。