正如标题所示,文章主要是围绕Quartz.Net作业调度框架话题展开的,内容出自博主学习官方Examples的学习心得与体会,文中难免会有错误之处,还请指出得以指教。
本篇关于Quart.Net涉及到的关键词是参数传递,状态维持,异常处理。
一:传参
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using Quartz.Impl; using Common.Logging; namespace Quartz.Examples { public class YZRExample3:IExample { #region IExample 成员 public string Name { get { return GetType().Name; } } public void Run() { ILog log = LogManager.GetLogger(typeof(JobStateExample)); log.Info("------- 初始化 -------------------"); // First we must get a reference to a scheduler ISchedulerFactory sf = new StdSchedulerFactory(); IScheduler sched = sf.GetScheduler(); log.Info("------- 初始化完成 --------"); log.Info("------- 开始调度队列进程 ----------------"); DateTimeOffset startTime = DateBuilder.NextGivenSecondDate(null, 10); IJobDetail job1 = JobBuilder.Create<ColorJob>() .WithIdentity("job1", "group1") .Build(); ISimpleTrigger trigger1 = (ISimpleTrigger)TriggerBuilder.Create() .WithIdentity("trigger1", "group1") .StartAt(startTime) .WithSimpleSchedule(x => x.WithIntervalInSeconds(10).WithRepeatCount(4)) .Build(); //将初始化参数传递到作业中 job1.JobDataMap.Put(ColorJob.FavoriteColor, "Green"); job1.JobDataMap.Put(ColorJob.ExecutionCount, 1); DateTimeOffset scheduleTime1 = sched.ScheduleJob(job1, trigger1); log.Info(string.Format("{0} will run at: {1} and repeat: {2} times, every {3} seconds", job1.Key, scheduleTime1.ToString("r"), trigger1.RepeatCount, trigger1.RepeatInterval.TotalSeconds)); log.Info("------- Starting Scheduler ----------------"); // All of the jobs have been added to the scheduler, but none of the jobs // will run until the scheduler has been started sched.Start(); log.Info("------- Started Scheduler -----------------"); log.Info("------- Waiting 60 seconds... -------------"); try { // wait five minutes to show jobs Thread.Sleep(300 * 1000); // executing... } catch (ThreadInterruptedException) { } log.Info("------- Shutting Down ---------------------"); sched.Shutdown(true); log.Info("------- Shutdown Complete -----------------"); SchedulerMetaData metaData = sched.GetMetaData(); log.Info(string.Format("Executed {0} jobs.", metaData.NumberOfJobsExecuted)); } #endregion } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Quartz.Examples { using Common.Logging; /// <summary> /// This is just a simple job that receives parameters and maintains state. /// 演示一个简单的传接参以及状态保持===>主程序可以通过jobDataMap初始化参数,job类通过JobDataMap来获取参数,并且状态保持。 /// </summary> /// <author>Bill Kratzer</author> /// <author>Marko Lahma (.NET)</author> [PersistJobDataAfterExecution] [DisallowConcurrentExecution] public class ColorJob : IJob { private static readonly ILog log = LogManager.GetLogger(typeof(ColorJob)); // parameter names specific to this job public const string FavoriteColor = "favorite color"; public const string ExecutionCount = "count"; // Since Quartz will re-instantiate a class every time it // gets executed, members non-static member variables can // not be used to maintain state! private int counter = 1; /// <summary> /// Called by the <see cref="IScheduler" /> when a /// <see cref="ITrigger" /> fires that is associated with /// the <see cref="IJob" />. /// </summary> public virtual void Execute(IJobExecutionContext context) { // This job simply prints out its job name and the // date and time that it is running JobKey jobKey = context.JobDetail.Key; // Grab and print passed parameters JobDataMap data = context.JobDetail.JobDataMap; string favoriteColor = data.GetString(FavoriteColor); int count = data.GetInt(ExecutionCount); log.InfoFormat( "ColorJob: {0} executing at {1}\n favorite color is {2}\n execution count (from job map) is {3}\n execution count (from job member variable) is {4}", jobKey, DateTime.Now.ToString("r"), favoriteColor, count, counter); // increment the count and store it back into the job map so that job state can be properly maintained //增加计数并将其储存在job map中,以使job state得到适当的维护 count++; data.Put(ExecutionCount, count); // Increment the local member variable //增量本地成员变量 // This serves no real purpose since job state can not be maintained via member variables! // 这并不是真正的目的,因为job state不能通过成员变量来保持! counter++; } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Quartz.Examples { /// <summary> /// Interface for examples. /// </summary> /// <author>Marko Lahma (.NET)</author> public interface IExample { string Name { get; } void Run(); } }
对于以上代码还有不熟悉的,建议请先看前面几章的介绍,这有助于你理解接下来讨论的话题。
上述代码我们构建了一个简单的轮询调度作业,唯一只有两行代码比较陌生新鲜:
//将初始化参数传递到作业中 job1.JobDataMap.Put(ColorJob.FavoriteColor, "Green"); job1.JobDataMap.Put(ColorJob.ExecutionCount, 1);
IJob接口下定义了public virtual void Execute(IJobExecutionContext context);方法,注意到这个方法定义了一个IJobExecutionContext类型的参数,可以通过前面的几个例子知道这个context可以得到:
JobKey jobKey = context.JobDetail.Key;//作业的名称
String description=context.JobDetail.Description;//作业的描述
bool durable=context.JobDetail.Durable;
这里还可以得到另外一个,没错没就是JobDataMap(Key-Value):
JobDataMap data = context.JobDetail.JobDataMap;
我们可以通过JobDataMap来传送参数给作业类,并且JobDataMap里的参数键值对会得到状态保持。而且,上面例子也说明了在作业类定义成员变量将不会得到状态维持。
PS:Map在java中是一个接口类型,有HashMap,TreeMap,它们本身都是键值对的形式保存数据。
除了job1.JobDataMap.Put()这种显示的方式传送参数之外,还可以使用UsingJobData()来传递参数给作业类:
在定义作业类的时候使用UsingJobData()传递参数:
IJobDetail job = JobBuilder.Create<ColorJob>() .WithIdentity("statefulJob1", "group1") .UsingJobData(StatefulDumbJob.ExecutionDelay, 10000L) .Build();
在ColorJob中JobDataMap data = context.JobDetail.JobDataMap; data会包含使用UsingJobData()传递的参数:
二:作业与作业(同样的作业)之间参数以及状态是否会互相干扰?
添加多一个作业ColorJob,并且也传送参数,代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using Quartz.Impl; using Common.Logging; namespace Quartz.Examples { public class YZRExample3:IExample { #region IExample 成员 public string Name { get { return GetType().Name; } } public void Run() { ILog log = LogManager.GetLogger(typeof(JobStateExample)); log.Info("------- 初始化 -------------------"); // First we must get a reference to a scheduler ISchedulerFactory sf = new StdSchedulerFactory(); IScheduler sched = sf.GetScheduler(); log.Info("------- 初始化完成 --------"); log.Info("------- 开始调度队列进程 ----------------"); // get a "nice round" time a few seconds in the future.... DateTimeOffset startTime = DateBuilder.NextGivenSecondDate(null, 10); // job1 will only run 5 times (at start time, plus 4 repeats), every 10 seconds IJobDetail job1 = JobBuilder.Create<ColorJob>() .WithIdentity("job1", "group1") .Build(); ISimpleTrigger trigger1 = (ISimpleTrigger)TriggerBuilder.Create() .WithIdentity("trigger1", "group1") .StartAt(startTime) .WithSimpleSchedule(x => x.WithIntervalInSeconds(10).WithRepeatCount(4)) .Build(); // pass initialization parameters into the job //将初始化参数传递到作业中 job1.JobDataMap.Put(ColorJob.FavoriteColor, "Green"); job1.JobDataMap.Put(ColorJob.ExecutionCount, 1); // schedule the job to run DateTimeOffset scheduleTime1 = sched.ScheduleJob(job1, trigger1); log.Info(string.Format("{0} will run at: {1} and repeat: {2} times, every {3} seconds", job1.Key, scheduleTime1.ToString("r"), trigger1.RepeatCount, trigger1.RepeatInterval.TotalSeconds)); //作业与作业之间的状态是分开的,不会互相干扰 // job2 will also run 5 times, every 10 seconds IJobDetail job2 = JobBuilder.Create<ColorJob>() .WithIdentity("job2", "group1") .Build(); ISimpleTrigger trigger2 = (ISimpleTrigger)TriggerBuilder.Create() .WithIdentity("trigger2", "group1") .StartAt(startTime) .WithSimpleSchedule(x => x.WithIntervalInSeconds(10).WithRepeatCount(4)) .Build(); // pass initialization parameters into the job // this job has a different favorite color! job2.JobDataMap.Put(ColorJob.FavoriteColor, "Red"); job2.JobDataMap.Put(ColorJob.ExecutionCount, 1); // schedule the job to run DateTimeOffset scheduleTime2 = sched.ScheduleJob(job2, trigger2); log.Info(string.Format("{0} will run at: {1} and repeat: {2} times, every {3} seconds", job2.Key, scheduleTime2.ToString("r"), trigger2.RepeatCount, trigger2.RepeatInterval.TotalSeconds)); log.Info("------- Starting Scheduler ----------------"); // All of the jobs have been added to the scheduler, but none of the jobs // will run until the scheduler has been started sched.Start(); log.Info("------- Started Scheduler -----------------"); log.Info("------- Waiting 60 seconds... -------------"); try { // wait five minutes to show jobs Thread.Sleep(300 * 1000); // executing... } catch (ThreadInterruptedException) { } log.Info("------- Shutting Down ---------------------"); sched.Shutdown(true); log.Info("------- Shutdown Complete -----------------"); SchedulerMetaData metaData = sched.GetMetaData(); log.Info(string.Format("Executed {0} jobs.", metaData.NumberOfJobsExecuted)); } #endregion } }
当你运行代码看了打印信息之后,就可以得知,作业与作业之间是相互区分的,不会互相干扰。
三:延迟执行
存在一种可能就是在轮询的时间周期很短,但我们的作业执行需要花费的时间较长,这样的情况下我们需要让下一次轮询的时机到来之时先等待作业执行完毕,再触发下一次时机。
这样子会导致的结果是:假如作业执行花费的时间为10s,而程序定制的轮询周期为每5s一次,这样等价于下一次轮询到的时机到来时上一次作业还没
来得及完成,所以下一次触发必须先等待作业完成之后再触发。很明显结果就是轮询周期不再是指定的每5s一次,而是10s一次。
//延迟10秒再继续重复作业 IJobDetail job = JobBuilder.Create<StatefulDumbJob>() .WithIdentity("statefulJob1", "group1") .UsingJobData(StatefulDumbJob.ExecutionDelay, 10000L) .Build(); ISimpleTrigger trigger = (ISimpleTrigger)TriggerBuilder.Create() .WithIdentity("trigger1", "group1") .StartAt(startTime) .WithSimpleSchedule(x => x.WithIntervalInSeconds(5).RepeatForever()) .Build(); DateTimeOffset ft = sched.ScheduleJob(job, trigger); log.Info(string.Format("{0} will run at: {1} and repeat: {2} times, every {3} seconds", job.Key, ft.ToString("r"), trigger.RepeatCount, trigger.RepeatInterval.TotalSeconds)); */ //比较合适的写法是:当没有在轮询时机触发时使用WithMisfireHandlingInstructionNowWithExistingCount() // statefulJob2 will run every three seconds // (but it will delay for ten seconds - and therefore purposely misfire after a few iterations) IJobDetail job = JobBuilder.Create<StatefulDumbJob>() .WithIdentity("statefulJob2", "group1") .UsingJobData(StatefulDumbJob.ExecutionDelay, 10000L) .Build(); ISimpleTrigger trigger = (ISimpleTrigger)TriggerBuilder.Create() .WithIdentity("trigger2", "group1") .StartAt(startTime) .WithSimpleSchedule(x => x .WithIntervalInSeconds(3) .RepeatForever() .WithMisfireHandlingInstructionNowWithExistingCount()) // set misfire instructions .Build(); DateTimeOffset ft = sched.ScheduleJob(job, trigger); log.Info(string.Format("{0} will run at: {1} and repeat: {2} times, every {3} seconds", job.Key, ft.ToString("r"), trigger.RepeatCount, trigger.RepeatInterval.TotalSeconds));
四:异常JobExecutionException
1.发生异常时程序会抛出异常信息,并且恢复继续调度
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Common.Logging; namespace Quartz.Examples { /// <summary> /// A job dumb job that will throw a job execution exception. /// 异常处理: /// 在作业发生异常的时候,抛出一个异常,并且程序对异常进行处理之后,RefireImmediately会马上恢复作业的继续调度 /// </summary> /// <author>Bill Kratzer</author> /// <author>Marko Lahma (.NET)</author> [PersistJobDataAfterExecution] [DisallowConcurrentExecution] public class BadJob1 : IJob { // Logging private static readonly ILog log = LogManager.GetLogger(typeof(BadJob1)); /// <summary> /// Called by the <see cref="IScheduler" /> when a Trigger" /> /// fires that is associated with the <see cref="IJob" />. /// </summary> public virtual void Execute(IJobExecutionContext context) { JobKey jobKey = context.JobDetail.Key; JobDataMap dataMap = context.JobDetail.JobDataMap; int denominator = dataMap.GetInt("denominator"); log.InfoFormat("{0} with denominator {1}", "---" + jobKey + " executing at " + DateTime.Now, denominator); // a contrived example of an exception that // will be generated by this job due to a // divide by zero error (only on first run) try { int calculation = 4815 / denominator; } catch (Exception e) { log.Info("--- Error in job!"); JobExecutionException e2 = new JobExecutionException(e); // fix denominator so the next time this job run // it won't fail again dataMap.Put("denominator", "1"); // this job will refire immediately e2.RefireImmediately = true; throw e2; } log.InfoFormat("---{0} completed at {1}", jobKey, DateTime.Now.ToString("r")); } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Common.Logging; using Quartz.Impl; using System.Threading; namespace Quartz.Examples { public class YZRExample5:IExample { #region IExample 成员 public string Name { get { return GetType().Name; } } public void Run() { ILog log = LogManager.GetLogger(typeof(JobExceptionExample)); 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 -------------------"); DateTimeOffset startTime = DateBuilder.NextGivenSecondDate(null, 15); //模拟作业内部发生异常,但进过异常处理之后,作业会继续进行调度 IJobDetail job = JobBuilder.Create<BadJob1>() .WithIdentity("badJob1", "group1") .UsingJobData("denominator", "0")//使用除数为0模拟异常 .Build(); ISimpleTrigger trigger = (ISimpleTrigger)TriggerBuilder.Create() .WithIdentity("trigger1", "group1") .StartAt(startTime) .WithSimpleSchedule(x => x.WithIntervalInSeconds(10).RepeatForever()) .Build(); DateTimeOffset ft = sched.ScheduleJob(job, trigger); log.Info(job.Key + " will run at: " + ft + " and repeat: " + trigger.RepeatCount + " times, every " + trigger.RepeatInterval.TotalSeconds + " seconds"); log.Info("------- Starting Scheduler ----------------"); sched.Start(); log.Info("------- Started Scheduler -----------------"); // sleep for 30 seconds try { Thread.Sleep(TimeSpan.FromSeconds(30)); } catch (ThreadInterruptedException) { } log.Info("------- Shutting Down ---------------------"); sched.Shutdown(false); log.Info("------- Shutdown Complete -----------------"); SchedulerMetaData metaData = sched.GetMetaData(); log.Info(string.Format("Executed {0} jobs.", metaData.NumberOfJobsExecuted)); } #endregion } }
2.发生异常时会抛出异常信息,并且停止调度
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Common.Logging; namespace Quartz.Examples { /// <summary> /// A job dumb job that will throw a job execution exception. /// 抛出异常,并且与当前作业有关的所有trigger都将会被停止调度 /// </summary> /// <author>Bill Kratzer</author> /// <author>Marko Lahma (.NET)</author> [PersistJobDataAfterExecution] [DisallowConcurrentExecution] public class BadJob2 : IJob { // Logging private static readonly ILog log = LogManager.GetLogger(typeof(BadJob2)); /// <summary> /// Called by the <see cref="IScheduler" /> when a <see cref="ITrigger" /> /// fires that is associated with the <see cref="IJob" />. /// <para> /// The implementation may wish to set a result object on the /// JobExecutionContext before this method exits. The result itself /// is meaningless to Quartz, but may be informative to /// <see cref="IJobListener" />s or /// <see cref="ITriggerListener" />s that are watching the job's /// execution. /// </para> /// </summary> /// <param name="context">Execution context.</param> public virtual void Execute(IJobExecutionContext context) { JobKey jobKey = context.JobDetail.Key; log.InfoFormat("---{0} executing at {1}", jobKey, System.DateTime.Now.ToString("r")); // a contrived example of an exception that will be generated by this job due to a divide by zero error // 在一个作业里人为的抛出一个异常 // try { int zero = 0; int calculation = 4815 / zero; } catch (System.Exception e) { log.Info("--- Error in job!"); JobExecutionException e2 = new JobExecutionException(e); // Quartz will automatically unschedule // all triggers associated with this job // so that it does not run again e2.UnscheduleAllTriggers = true; throw e2; } log.InfoFormat("---{0} completed at {1}", jobKey, System.DateTime.Now.ToString("r")); } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Common.Logging; using Quartz.Impl; using System.Threading; namespace Quartz.Examples { public class YZRExample5:IExample { #region IExample 成员 public string Name { get { return GetType().Name; } } public void Run() { ILog log = LogManager.GetLogger(typeof(JobExceptionExample)); 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 -------------------"); DateTimeOffset startTime = DateBuilder.NextGivenSecondDate(null, 15); //引起异常的作业将会停止所有相关的trigger的运行并且抛出异常 job = JobBuilder.Create<BadJob2>() .WithIdentity("badJob2", "group1") .Build(); trigger = (ISimpleTrigger)TriggerBuilder.Create() .WithIdentity("trigger2", "group1") .StartAt(startTime) .WithSimpleSchedule(x => x.WithIntervalInSeconds(5).RepeatForever()) .Build(); ft = sched.ScheduleJob(job, trigger); log.Info(string.Format("{0} will run at: {1} and repeat: {2} times, every {3} seconds", job.Key, ft.ToString("r"), trigger.RepeatCount, trigger.RepeatInterval.TotalSeconds)); log.Info("------- Starting Scheduler ----------------"); sched.Start(); log.Info("------- Started Scheduler -----------------"); // sleep for 30 seconds try { Thread.Sleep(TimeSpan.FromSeconds(30)); } catch (ThreadInterruptedException) { } log.Info("------- Shutting Down ---------------------"); sched.Shutdown(false); log.Info("------- Shutdown Complete -----------------"); SchedulerMetaData metaData = sched.GetMetaData(); log.Info(string.Format("Executed {0} jobs.", metaData.NumberOfJobsExecuted)); } #endregion } }