正如标题所示,文章主要是围绕Quartz.Net作业调度框架话题展开的,内容出自博主学习官方Examples的学习心得与体会,文中难免会有错误之处,还请指出得以指教。
一: Calendar
前面演示过根据秒-分-时 日-月-星期等触发时机来定义一个轮询的作业调度,在实际生活中,除此之外还有根据日历来调度作业,根据日历定义触发轮询周期也是比较常用常见的功能。
在Quartz.Net中日历是由AnnualCalendar类定义的,实例化一个AnnualCalendar对象,往这个对象添加自定义的日期构成自定义的一个日历触发时机集合。
举个例子:
//日历对象 AnnualCalendar holidays = new AnnualCalendar(); // 元旦 DateTime NewYearDay = new DateTime(DateTime.UtcNow.Year, 1, 1); holidays.SetDayExcluded(NewYearDay , true); // 国庆节 DateTime NationalDay= new DateTime(DateTime.UtcNow.Year, 10, 1); holidays.SetDayExcluded(NationalDay, true); // 光棍节 DateTime SinglesDay= new DateTime(DateTime.UtcNow.Year, 11, 11); holidays.SetDayExcluded(SinglesDay, true);
有了日历对象之后,需要将日历对象附加到调度实例上,并且在触发器中使用ModifiedByCalendar("日历对象")来指定按照日历对象进行调度。
下面贴出根据日历对象指定的日期进行作业调度的代码(仅供参考):
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using Quartz.Impl.Calendar; using Quartz.Impl; using Common.Logging; namespace Quartz.Examples { /// <summary> /// This example will demonstrate how calendars can be used /// to exclude periods of time when scheduling should not /// take place. /// 一个根据日历(节假日)来设定调度作业的演示 /// </summary> /// <author>Marko Lahma (.NET)</author> public class CalendarExample : IExample { public string Name { get { return GetType().Name; } } public virtual void Run() { ILog log = LogManager.GetLogger(typeof(CalendarExample)); log.Info("------- Initializing ----------------------"); // First we must get a reference to a scheduler ISchedulerFactory sf = new StdSchedulerFactory(); IScheduler sched = sf.GetScheduler(); log.Info("------- Initialization Complete -----------"); log.Info("------- Scheduling Jobs -------------------"); // Add the holiday calendar to the schedule //就是自定义节假日 比如我们可以事先根据日历设定7月4号,10月31号,12月25号作为调度时机 AnnualCalendar holidays = new AnnualCalendar(); // fourth of July (July 4) DateTime fourthOfJuly = new DateTime(DateTime.UtcNow.Year, 7, 4); holidays.SetDayExcluded(fourthOfJuly, true); // halloween (Oct 31) DateTime halloween = new DateTime(DateTime.UtcNow.Year, 10, 31);//10月31号 holidays.SetDayExcluded(halloween, true); // christmas (Dec 25) DateTime christmas = new DateTime(DateTime.UtcNow.Year, 12, 25); holidays.SetDayExcluded(christmas, true); // tell the schedule about our holiday calendar sched.AddCalendar("holidays", holidays, false, false); //设定开启调度日历的时间 DateTimeOffset runDate = DateBuilder.DateOf(10, 0, 0, 31, 10);//10.31早上10点开启调度作业 IJobDetail job = JobBuilder.Create<SimpleJob>() .WithIdentity("job1", "group1") .Build(); ISimpleTrigger trigger = (ISimpleTrigger)TriggerBuilder.Create() .WithIdentity("trigger1", "group1") .StartAt(runDate) .WithSimpleSchedule(x => x.WithIntervalInHours(1).RepeatForever()) .ModifiedByCalendar("holidays") .Build(); // schedule the job and print the first run date DateTimeOffset firstRunTime = sched.ScheduleJob(job, trigger); log.Info(string.Format("{0} will run at: {1} and repeat: {2} times, every {3} seconds", job.Key, firstRunTime.ToString("r"), trigger.RepeatCount, trigger.RepeatInterval.TotalSeconds)); log.Info("------- Starting Scheduler ----------------"); sched.Start(); log.Info("------- Waiting 30 seconds... --------------"); try { // wait 30 seconds to show jobs Thread.Sleep(30 * 1000); // executing... } catch (ThreadInterruptedException) { } // shut down the scheduler log.Info("------- Shutting Down ---------------------"); sched.Shutdown(true); log.Info("------- Shutdown Complete -----------------"); SchedulerMetaData metaData = sched.GetMetaData(); log.Info(string.Format("Executed {0} jobs.", metaData.NumberOfJobsExecuted)); } } }
二:监听对象
在Quartz.Net框架中提供了一个监听器IJobListener接口,实现该接口实例化一个子类,这个子类可以监听一个作业id来触发本身的void JobWasExecuted(IJobExecutionContext inContext, JobExecutionException inException)方法:这个方法里面带了IJobExecutionContext inContext参数,inContext.Scheduler其实就是程序的调度实例,我们知道通过调度实例可以添加作业以及触发器(定制一个轮询的调度任务)并且Start()开启执行任务。
这样看来,通过实现IJobListener接口得到监听器类中的JobWasExecuted()方法里可以再次定义轮询调度作业。
比如当需要满足以下需求时可以使用监听器来实现:
=>在A任务顺利开启执行后,轮询调度B任务。(此时B任务就定义在监听器类里面)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using Quartz.Impl.Matchers; using Quartz.Impl; using Common.Logging; namespace Quartz.Examples { public class ListenerExample : IExample { public string Name { get { return GetType().Name; } } public virtual void Run() { ILog log = LogManager.GetLogger(typeof(ListenerExample)); log.Info("------- Initializing ----------------------"); // First we must get a reference to a scheduler ISchedulerFactory sf = new StdSchedulerFactory(); IScheduler sched = sf.GetScheduler(); log.Info("------- Initialization Complete -----------"); log.Info("------- Scheduling Jobs -------------------"); // schedule a job to run immediately IJobDetail job = JobBuilder.Create<SimpleJob1>() .WithIdentity("job1") .Build(); ITrigger trigger = TriggerBuilder.Create() .WithIdentity("trigger1") .StartNow() .Build(); //IJobListener监听器接口,实现该接口得到自定义的Job1Listener类,在该类中实现JobWasExecuted()方法,在方法中可以添加作业触发器 //监听类的意义更在于在一个作业成功运行之后,触发绑定的另外一些操作,这些操作在监听类中定义并调度。 // Set up the listener //设定监听程序,实例话Job1Listener()监听类 IJobListener listener = new Job1Listener(); IMatcher<JobKey> matcher = KeyMatcher<JobKey>.KeyEquals(job.Key); sched.ListenerManager.AddJobListener(listener, matcher);//根据作业key为响应作业添加监听 sched.ScheduleJob(job, trigger); log.Info("------- Starting Scheduler ----------------"); sched.Start(); log.Info("------- Waiting 30 seconds... --------------"); try { // wait 30 seconds to show jobs Thread.Sleep(TimeSpan.FromSeconds(30)); // executing... } catch (ThreadInterruptedException) { } // shut down the scheduler log.Info("------- Shutting Down ---------------------"); sched.Shutdown(true); log.Info("------- Shutdown Complete -----------------"); SchedulerMetaData metaData = sched.GetMetaData(); log.Info(string.Format("Executed {0} jobs.", metaData.NumberOfJobsExecuted)); } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Common.Logging; namespace Quartz.Examples { ///监听程序可以附加到作业中,附加了监听器的作业在进行调度的时候,准备执行,否决,执行完毕三个状态 ///并且在监听程序可以实例化IJobDetail类创建新作业 public class Job1Listener : IJobListener { private static readonly ILog log = LogManager.GetLogger(typeof(Job1Listener)); public virtual string Name { get { return "job1_to_job2"; } } public virtual void JobToBeExecuted(IJobExecutionContext inContext) { log.Info("Job1Listener says: Job Is about to be 执行.");//执行 } public virtual void JobExecutionVetoed(IJobExecutionContext inContext) { log.Info("Job1Listener says: Job Execution was 否决.");//否决 } public virtual void JobWasExecuted(IJobExecutionContext inContext, JobExecutionException inException) { log.Info("Job1Listener says: Job was 执行 完毕."); // Simple job #2 //监听程序调度作业 IJobDetail job2 = JobBuilder.Create<SimpleJob2>() .WithIdentity("job2") .Build(); ITrigger trigger = TriggerBuilder.Create() .WithIdentity("job2Trigger") .StartNow() .Build(); try { // schedule the job to run! inContext.Scheduler.ScheduleJob(job2, trigger); } catch (SchedulerException e) { log.Warn("Unable to schedule job2!"); Console.Error.WriteLine(e.StackTrace); } } } }
三:插件配置
官方给出的名字叫做插件,其实我认为,这只是一种关于如何调度作业的配置。
有三种配置方式:
1.通过代码实例化NameValueCollection对象,往NameValueCollection对象以键值对形式赋值,然后在调度工厂对象中传入该NameValueCollection对象得到调度实例。
var properties = new NameValueCollection(); properties["quartz.plugin.triggHistory.type"] = "Quartz.Plugin.History.LoggingJobHistoryPlugin"; properties["quartz.plugin.jobInitializer.type"] = "Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin";//插件 properties["quartz.plugin.jobInitializer.fileNames"] = "quartz_jobs.xml";//读取这个配置文件 properties["quartz.plugin.jobInitializer.failOnFileNotFound"] = "true"; properties["quartz.plugin.jobInitializer.scanInterval"] = "120";//120秒一次 // First we must get a reference to a scheduler ISchedulerFactory sf = new StdSchedulerFactory(properties); IScheduler sched = sf.GetScheduler();
sched.Start();
通过这样方式无须再使用代码定义作业对象IJobDetail以及触发器ITrigger等,而是通过properties["quartz.plugin.jobInitializer.fileNames"]指定的xml文件来设置:
<?xml version="1.0" encoding="UTF-8"?> <job-scheduling-data xmlns="http://quartznet.sourceforge.net/JobSchedulingData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0"> <processing-directives> <overwrite-existing-data>true</overwrite-existing-data> </processing-directives> <schedule> <job> <name>jobName1</name> <group>jobGroup1</group> <description>jobDesciption1</description> <job-type>Quartz.Examples.SimpleJob3, Quartz.Examples</job-type> <durable>true</durable> <recover>false</recover> <job-data-map> <entry> <key>key0</key> <value>value0</value> </entry> <entry> <key>key1</key> <value>value1</value> </entry> <entry> <key>key2</key> <value>value2</value> </entry> </job-data-map> </job> <trigger> <simple> <name>simpleName</name> <group>simpleGroup</group> <description>SimpleTriggerDescription</description> <job-name>jobName1</job-name> <job-group>jobGroup1</job-group> <start-time>1982-06-28T18:15:00.0Z</start-time> <end-time>2020-05-04T18:13:51.0Z</end-time> <misfire-instruction>SmartPolicy</misfire-instruction> <repeat-count>100</repeat-count> <repeat-interval>3000</repeat-interval> </simple> </trigger> </schedule> </job-scheduling-data>
注意点:
1.1 quartz_jobs.xml必须设置为始终复制=>右键属性,复制到输出目录选项选择始终复制
1.2 要将NameValueCollection对象传入StdSchedulerFactory工厂中以得到调试实例对象
ISchedulerFactory sf = new StdSchedulerFactory(properties);
2.通过app.config或者web.config配置文件
通过这种方式定义调度作业的信息将会放置在app.config或web.config配置文件中,在代码中只需要得到一个无参的StdSchedulerFactory()实例对象,开启调度即可:
ISchedulerFactory sf = new StdSchedulerFactory(); IScheduler sched = sf.GetScheduler(); // start the schedule sched.Start();
app.config配置文件内容:
<?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <section name="quartz" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089" /> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" /> <sectionGroup name="common"> <section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging" /> </sectionGroup> </configSections> <common> <logging> <factoryAdapter type="Common.Logging.Log4Net.Log4NetLoggerFactoryAdapter, Common.Logging.Log4net1213"> <arg key="configType" value="INLINE" /> </factoryAdapter> </logging> </common> <log4net> <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender"> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%d [%t] %-5p %l - %m%n" /> </layout> </appender> <appender name="EventLogAppender" type="log4net.Appender.EventLogAppender"> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%d [%t] %-5p %l - %m%n" /> </layout> </appender> <root> <level value="INFO" /> <appender-ref ref="ConsoleAppender" /> <!-- uncomment to enable event log appending --> <!-- <appender-ref ref="EventLogAppender" /> --> </root> </log4net> <!-- We use quartz.config for this server, you can always use configuration section if you want to. Configuration section has precedence here. --> <quartz> <add key="quartz.plugin.triggHistory.type" value="Quartz.Plugin.History.LoggingJobHistoryPlugin"/> <add key="quartz.plugin.jobInitializer.type" value="Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin"/> <add key="quartz.plugin.jobInitializer.fileNames" value="quartz_jobs.xml"/> <add key="quartz.plugin.jobInitializer.failOnFileNotFound" value="true"/> <add key="quartz.plugin.jobInitializer.scanInterval" value="120"/> </quartz> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="Common.Logging" publicKeyToken="af08829b84f0328e" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-3.0.0.0" newVersion="3.0.0.0" /> </dependentAssembly> <dependentAssembly> <assemblyIdentity name="log4net" publicKeyToken="669e0ddf0bb1aa2a" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-1.2.13.0" newVersion="1.2.13.0" /> </dependentAssembly> </assemblyBinding> </runtime> </configuration>
在app.config中会指定调度任务信息的一个路径,比如quartz_jobs.xml文件,通过读取这个xml文件来获取调度任务。
2.1 quartz_jobs.xml必须设置为始终复制=>右键属性,复制到输出目录选项选择始终复制
3.通过quartz.config配置文件
# You can configure your scheduler in either <quartz> configuration section # or in quartz properties file # Configuration section has precedence quartz.scheduler.instanceName = ServerScheduler # configure thread pool info quartz.threadPool.type = Quartz.Simpl.SimpleThreadPool, Quartz quartz.threadPool.threadCount = 10 quartz.threadPool.threadPriority = Normal #--------------------------------*************plugin配置------------------------------------ # job initialization plugin handles our xml reading, without it defaults are used quartz.plugin.xml.type = Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin, Quartz quartz.plugin.xml.fileNames = ~/quartz_jobs.xml # export this server to remoting context quartz.scheduler.exporter.type = Quartz.Simpl.RemotingSchedulerExporter, Quartz quartz.scheduler.exporter.port = 555 quartz.scheduler.exporter.bindName = QuartzScheduler quartz.scheduler.exporter.channelType = tcp quartz.scheduler.exporter.channelName = httpQuartz
在代码中只需要得到一个无参的StdSchedulerFactory()实例对象,开启调度即可:
ISchedulerFactory sf = new StdSchedulerFactory(); IScheduler sched = sf.GetScheduler(); // start the schedule sched.Start();
3.1 quartz_jobs.xml必须设置为始终复制=>右键属性,复制到输出目录选项选择始终复制
3.2 quartz.config必须设置为始终复制=>右键属性,复制到输出目录选项选择始终复制