正如标题所示,文章主要是围绕Quartz.Net作业调度框架话题展开的,内容出自博主学习官方Examples的学习心得与体会,文中难免会有错误之处,还请指出得以指教。
在进入Quartz.Net之前,先来看一下Quartz.Net框架运行环境要准备的一些东西.
首先需要准备以下程序集:
1.Common.Logging
2.Common.Logging.Core
3.Common.Logging.Log4Net1213
4.log4net
5.Topshelf
6.Quartz
需要稍微注意一下彼此版本号要对应,博主的运行环境中依次版本号为(仅供参考):Common.Logging 3.0.0.0 ,Common.Logging.Core 3.0.0.0,Common.Logging.Log4Net1213 3.0.0.0 ,log4net 1.2.13.0 ,Quartz 2.3.2.0 ,Topshelf 3.1.135.0
ps:博主这里新建的控制台程序Quartz.Examples,程序集版本.net framework 4.0
一:接下来进入主题,首先先来看一段代码:
TIP:在接下来的全部例子中main函数都是使用以下代码,所以在贴出代码的时候将不再重复贴出main函数相关代码。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Quartz.Util; using System.Reflection; namespace Quartz.Examples { class Program { static void Main(string[] args) { try { //反射得到当前运行程序集 Assembly asm = Assembly.GetExecutingAssembly(); //获取所有类 Type[] types = asm.GetTypes(); IDictionary<int, Type> typeMap = new Dictionary<int, Type>(); int counter = 1; Console.WriteLine("Select example to run: "); List<Type> typeList = new List<Type>(); //循环遍历当前运行程序集的所有类 foreach (Type t in types) { //将实现了IExample接口的类加入到typeList集合中 if (new List<Type>(t.GetInterfaces()).Contains(typeof(IExample))) { typeList.Add(t); } } typeList.Sort(new TypeNameComparer()); //循环将序号 类名 加入到typeMap字典中 foreach (Type t in typeList) { string counterString = string.Format("[{0}]", counter).PadRight(4); Console.WriteLine("{0} {1} {2}", counterString, t.Namespace.Substring(t.Namespace.LastIndexOf(".") + 1), t.Name); typeMap.Add(counter++, t); } Console.WriteLine(); Console.Write("> "); //选择要运行的类的序号 int num = Convert.ToInt32(Console.ReadLine()); //获取该类的Type Type eType = typeMap[num]; //得到该类的实例 IExample example = ObjectUtils.InstantiateType<IExample>(eType); //运行Run() example.Run(); Console.WriteLine("Example run successfully."); } catch (Exception ex) { Console.WriteLine("Error running example: " + ex.Message); Console.WriteLine(ex.ToString()); } Console.Read(); } /// <summary> /// 用于排序的比较器 /// </summary> public class TypeNameComparer : IComparer<Type> { public int Compare(Type t1, Type t2) { if (t1.Namespace.Length > t2.Namespace.Length) { return 1; } if (t1.Namespace.Length < t2.Namespace.Length) { return -1; } return t1.Namespace.CompareTo(t2.Namespace); } } } }
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 gets fired off many times by example 2. /// </summary> /// <author>Bill Kratzer</author> /// <author>Marko Lahma (.NET)</author> public class SimpleJob : IJob { private static readonly ILog log = LogManager.GetLogger(typeof(SimpleJob)); /// <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) { //这个作业演示简单的打印出作业本身的名称以及正在运行的日期和时间 JobKey jobKey = context.JobDetail.Key;//作业的名称 String description=context.JobDetail.Description;//作业的描述 bool durable=context.JobDetail.Durable;//是否持续着 log.InfoFormat("SimpleJob says: {0} executing at {1} and Description is {2} and durable is {3}", jobKey, DateTime.Now.ToString("r"), description,durable); } } }
using System.Text; using Quartz.Impl; using Common.Logging; using System.Threading; namespace Quartz.Examples { public class YZRExample:IExample { #region IExample 成员 public string Name { get { return GetType().Name; } } public void Run() { ILog log = LogManager.GetLogger(typeof(SimpleTriggerExample)); log.Info("------- 开始初始化 -------------------"); // 使用工厂得到调度实例 ISchedulerFactory sf = new StdSchedulerFactory(); IScheduler sched = sf.GetScheduler(); log.Info("------- 初始化完成 --------"); log.Info("------- 调度作业 ----------------"); DateTimeOffset startTime = DateBuilder.NextGivenSecondDate(DateTimeOffset.UtcNow.AddMilliseconds(5), 15); //作业1将会通过startTime指定触发,并且只会运行一次 IJobDetail job = JobBuilder.Create<SimpleJob>() .WithIdentity("job1", "group1") .Build(); ISimpleTrigger trigger = (ISimpleTrigger)TriggerBuilder.Create() .WithIdentity("trigger1", "group1") .StartAt(startTime) .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 -----------------"); log.Info("------- Waiting five minutes... ------------"); try { // wait five minutes to show jobs Thread.Sleep(TimeSpan.FromMinutes(5)); // 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 { /// <summary> /// Interface for examples. /// </summary> /// <author>Marko Lahma (.NET)</author> public interface IExample { string Name { get; } void Run(); } }
运行的主要结果如下:
2016-04-23 01:30:04,760 [1] INFO Quartz.Examples.YZRExample.Run(C:\Users\Admini
strator\Desktop\Quatorz\Quartz.Examples\Quartz.Examples\YZRExample.cs:172)
- ------ Started Scheduler -----------------
2016-04-23 01:30:04,761 [1] INFO Quartz.Examples.YZRExample.Run(C:\Users\Administrator\Desktop\Quatorz\Quartz.Examples\Quartz.Examples\YZRExample.cs:232)
- ------ Waiting five minutes... ------------
2016-04-23 01:30:15,039 [DefaultQuartzScheduler_Worker-1] INFO Quartz.Examples.
simpleJob.Execute(C:\Users\Administrator\Desktop\Quatorz\Quartz.Examples\Quartz.
examples\SimpleJob.cs:31) -
SimpleJob says: group1.job1 executing at Sat, 23 Apr
2016 01:30:15 GMT and Description is and durable is False
从上面的部分打印信息可知,SimpleJob作业被调度了一次,并且是发生在第15秒这个时机。
你可能会疑惑,为什么在15秒的时候打印信息?
从头到尾来看一下代码:
// 使用工厂得到调度实例 ISchedulerFactory sf = new StdSchedulerFactory(); IScheduler sched = sf.GetScheduler();
在定义任何作业被调度之前,都需要使用StdSchedulerFactory()得到一个IScheduler 调度实例,通过这个调度实例来添加作业队列以及相对应的触发器。
DateTimeOffset startTime = DateBuilder.NextGivenSecondDate(DateTimeOffset.UtcNow.AddMilliseconds(5), 15);
DateTimeOffset NextGivenSecondDate(DateTimeOffset? date, int secondBase):
startTime定义的是作业第一次运行的时机,通过DateBuilder.NextGivenSecondDate(DateTimeOffset date,int secondBase);方法得到这个时机,
这个方法具体的意思是指:
当date为null时,startTime的值将会等于DateTime.Now当前时间
secondBase是指时间的第一个倍数,0<=secondBase<=59
比如举个例子:
在程序运行后的一分钟之后,第一个能整除15的秒数(15s,30s,45s,60s)触发作业,那么这个startTime可以这么声明:
DateTimeOffset startTime = DateBuilder.NextGivenSecondDate(DateTimeOffset.UtcNow.AddMinutes(1), 15);
UtcNow值得是时区时间,Now是本地时间(Now本质也是先取时区时间UtcNow再进行转换成本地时间)
IJobDetail job = JobBuilder.Create<SimpleJob>() .WithIdentity("job1", "group1") .Build();
IJobDetail 接口定义的是作业类,每一个作业类都有一个作业id和组id。
ISimpleTrigger trigger = (ISimpleTrigger)TriggerBuilder.Create() .WithIdentity("trigger1", "group1") .StartAt(startTime) .Build();
ISimpleTrigger接口定义的是触发器类,每一个Tirgger都有一个触发器id和组id。在触发器中指定触发的第一次时机。
//调度开始运行 DateTimeOffset? ft = sched.ScheduleJob(job, trigger); log.Info(job.Key + " will run at: " + ft + " and repeat: " + trigger.RepeatCount + " times, every " + trigger.RepeatInterval.TotalSeconds + " seconds");
调度实例sched指定作业类和触发器添加到作业队列中。
sched.Start();
需要sched.Start()才会开始调度队列中的作业。
二:如何指定一个作业重复调度的次数以及调度时间间隔呢?
博主认为作业的如何调度是在Tirgger中指定的,而作业本身相关的是定义在Job中,从而可知,作业重复调度的次数以及调度时间间隔这些调度机制是在Tirgger中指定。将上面的tirgger稍微修改一下,代码如下:
trigger = (ISimpleTrigger)TriggerBuilder.Create() .WithIdentity("trigger3", "group1") .StartAt(startTime) .WithSimpleSchedule(x => x.WithIntervalInSeconds(10).WithRepeatCount(10))//每10秒运行一次,并且重复10次(运行一次重复10次,所以将会运行11次) .Build();
当然也可以指定无限重复轮询:
trigger = (ISimpleTrigger)TriggerBuilder.Create() .WithIdentity("trigger6", "group1") .StartAt(startTime) .WithSimpleSchedule(x => x.WithIntervalInSeconds(40).RepeatForever()) .Build();
三:调度实例sched手动加入触发器
调度实例可以手动加入触发器,这样只需要定义作业类:
//手动加入触发器job8 job = JobBuilder.Create<SimpleJob>() .WithIdentity("job8", "group1") .StoreDurably() .Build(); sched.AddJob(job, true); log.Info("'Manually' triggering job8..."); sched.TriggerJob(new JobKey("job8", "group1"));
四:补充
1.当有多个作业IJobDetail时,可以多次添加到sched调度实例中,sched调度实例开启之后,实际上会根据优先级来轮询作业。
2.一个作业可以包含多个触发器,比如说,job1和tirgger1添加到sched之外,还可以job1和tirgger2再添加到sched调度队列中。
//这里再演示一个job拥有多个trigger,使用ForJob("作业对象")来实现 //这一次将只会重复2次每隔10秒 trigger = (ISimpleTrigger)TriggerBuilder.Create() .WithIdentity("trigger3", "group2") .StartAt(startTime) .WithSimpleSchedule(x => x.WithIntervalInSeconds(10).WithRepeatCount(2)) .ForJob(job)//为作业添加tirgger .Build(); ft = sched.ScheduleJob(trigger);
3.可以在定义触发器的时候定义触发时间startTime
//演示在trigger中指定触发时间 job = JobBuilder.Create<SimpleJob>() .WithIdentity("job5", "group1") .Build(); trigger = (ISimpleTrigger)TriggerBuilder.Create() .WithIdentity("trigger5", "group1") .StartAt(DateBuilder.FutureDate(1, IntervalUnit.Minute)) .Build(); ft = sched.ScheduleJob(job, trigger);
4.触发器中使用RepeatForever可以无限次轮询
job = JobBuilder.Create<SimpleJob>() .WithIdentity("job6", "group1") .Build(); trigger = (ISimpleTrigger)TriggerBuilder.Create() .WithIdentity("trigger6", "group1") .StartAt(startTime) .WithSimpleSchedule(x => x.WithIntervalInSeconds(40).RepeatForever()) .Build(); ft = sched.ScheduleJob(job, trigger);
5.最后强调一下,当job和tirgger都定义好并且添加到调度实例中之后,需要开启调度:
sched.Start();
只有start之后,调度实例中作业队列才会被开始调度。另外,在start()之后,还有继续往调度实例中添加(作业触发器),即使是在start之后添加到队列中去的,只需要将tirgger设置为重新启动,相关的作业还是会被正常调度。
// jobs can be re-scheduled... // job 7 will run immediately and repeat 10 times for every second log.Info("------- Rescheduling... --------------------"); trigger = (ISimpleTrigger)TriggerBuilder.Create() .WithIdentity("trigger7", "group1") .StartAt(startTime) .WithSimpleSchedule(x => x.WithIntervalInMinutes(5).WithRepeatCount(20)) .Build(); ft = sched.RescheduleJob(trigger.Key, trigger);//将触发器指定为该trigger("trigger7", "group1")的作业将会被重新启动
6.移出执行计划
ISchedulerFactory sf = new StdSchedulerFactory(); IScheduler sched = sf.GetScheduler(); var tirgger = new TriggerKey("TirggerName", "TirggerGroup"); sched.PauseTrigger(tirgger); var job = new JobKey("jobName","jobGroup"); sched.PauseJob(job);
sched.DeleteJob(JobKey.Create("jobName", "jobGroup"));