详解免费开源的DotNet任务调度组件Quartz.NET(.NET组件介绍之五)
很多的软件项目中都会使用到定时任务、定时轮询数据库同步,定时邮件通知等功能。.NETFramework具有“内置”定时器功能,通过System.Timers.Timer类。在使用Timer类需要面对的问题:计时器没有持久化机制;计时器具有不灵活的计划(仅能设置开始时间和重复间隔,没有基于日期,时间等);计时器不使用线程池(每个定时器一个线程);计时器没有真正的管理方案-你必须编写自己的机制,以便能够记住,组织和检索任务的名称等。
如果需要在.NET实现定时器的功能,可以尝试使用以下这款开源免费的组件Quartz.Net组件。目前Quartz.NET版本为3.0,修改了原来的一些问题:修复由于线程本地存储而不能与AdoJobStore协同工作的调度器信令;线程局部状态完全删除;quartz.serializer.type是必需的,即使非序列化RAMJobStore正在使用;JSON序列化错误地称为序列化回调。
一.Quart.NET概述:
Quartz是一个作业调度系统,可以与任何其他软件系统集成或一起使用。作业调度程序是一个系统,负责在执行预处理程序时执行(或通知)其他软件组件-确定(调度)时间到达。Quartz是非常灵活的,并且包含多个使用范例,可以单独使用或一起使用,以实现您所需的行为,并使您能够以您的项目看起来最“自然”的方式编写代码。组件的使用非常轻便,并且需要非常少的设置/配置-如果您的需求相对基础,它实际上可以使用“开箱即用”。Quartz是容错的,并且可以在系统重新启动之间保留(记住)您的预定作业。尽管Quartz对于在给定的时间表上简单地运行某些系统进程非常有用,但当您学习如何使用Quartz来驱动应用程序的业务流程时,Quartz的全部潜能可以实现。
Quartz是作为一个小的动态链接库(.dll文件)分发的,它包含所有的核心Quartz功能。此功能的主要接口(API)是调度程序接口。它提供简单的操作,如调度/非调度作业,启动/停止/暂停调度程序。如果你想安排你自己的软件组件执行,他们必须实现简单的Job接口,它包含方法execute()。如果希望在计划的触发时间到达时通知组件,则组件应实现TriggerListener或JobListener接口。主要的Quartz'进程'可以在您自己的应用程序或独立应用程序(使用远程接口)中启动和运行。
二.Quartz.NET主体类和方法解析:
1.StdSchedulerFactory类:创建QuartzScheduler实例。
///<summary> ///返回此工厂生成的调度程序的句柄。 ///</summary> ///<remarks> ///如果<seecref=“Initialize()”/>方法之一没有先前调用,然后是默认(no-arg)<seecref=“Initialize()”/>方法将被这个方法调用。 ///</remarks> publicvirtualISchedulerGetScheduler() { if(cfg==null) { Initialize(); } SchedulerRepositoryschedRep=SchedulerRepository.Instance; ISchedulersched=schedRep.Lookup(SchedulerName); if(sched!=null) { if(sched.IsShutdown) { schedRep.Remove(SchedulerName); } else { returnsched; } } sched=Instantiate(); returnsched; }
publicinterfaceISchedulerFactory { ///<summary> ///ReturnshandlestoallknownSchedulers(madebyanySchedulerFactory ///withinthisappdomain.). ///</summary> ICollection<IScheduler>AllSchedulers{get;} ///<summary> ///Returnsaclient-usablehandletoa<seecref="IScheduler"/>. ///</summary> ISchedulerGetScheduler(); ///<summary> ///ReturnsahandletotheSchedulerwiththegivenname,ifitexists. ///</summary> ISchedulerGetScheduler(stringschedName); }
2.JobDetailImpl:传递给定作业实例的详细信息属性。
///<summary> ///获取或设置与<seecref=“IJob”/>相关联的<seecref=“JobDataMap”/>。 ///</summary> publicvirtualJobDataMapJobDataMap { get { if(jobDataMap==null) { jobDataMap=newJobDataMap(); } returnjobDataMap; } set{jobDataMap=value;} }
3.JobKey:键由名称和组组成,名称必须是唯一的,在组内。如果只指定一个组,则默认组将使用名称。
[Serializable] publicsealedclassJobKey:Key<JobKey> { publicJobKey(stringname):base(name,null) { } publicJobKey(stringname,stringgroup):base(name,group) { } publicstaticJobKeyCreate(stringname) { returnnewJobKey(name,null); } publicstaticJobKeyCreate(stringname,stringgroup) { returnnewJobKey(name,group); } }
4.StdSchedulerFactory.Initialize():
///<summary> ///使用初始化<seecref=“ISchedulerFactory”/> ///给定键值集合对象的内容。 ///</summary> publicvirtualvoidInitialize(NameValueCollectionprops) { cfg=newPropertiesParser(props); ValidateConfiguration(); } protectedvirtualvoidValidateConfiguration() { if(!cfg.GetBooleanProperty(PropertyCheckConfiguration,true)) { //shouldnotvalidate return; } //determinecurrentlysupportedconfigurationkeysviareflection List<string>supportedKeys=newList<string>(); List<FieldInfo>fields=newList<FieldInfo>(GetType().GetFields(BindingFlags.Static|BindingFlags.Public|BindingFlags.FlattenHierarchy)); //chooseconstantstringfields fields=fields.FindAll(field=>field.FieldType==typeof(string)); //readvaluefromeachfield foreach(FieldInfofieldinfields) { stringvalue=(string)field.GetValue(null); if(value!=null&&value.StartsWith(ConfigurationKeyPrefix)&&value!=ConfigurationKeyPrefix) { supportedKeys.Add(value); } } //nowcheckagainstallowed foreach(stringconfigurationKeyincfg.UnderlyingProperties.AllKeys) { if(!configurationKey.StartsWith(ConfigurationKeyPrefix)||configurationKey.StartsWith(ConfigurationKeyPrefixServer)) { //don'tbotheriftrulyunknownproperty continue; } boolisMatch=false; foreach(stringsupportedKeyinsupportedKeys) { if(configurationKey.StartsWith(supportedKey,StringComparison.InvariantCulture)) { isMatch=true; break; } } if(!isMatch) { thrownewSchedulerConfigException("Unknownconfigurationproperty'"+configurationKey+"'"); } } }
三.Quartz.NET的基本应用:
下面提供一些较为通用的任务处理代码:
1.任务处理帮助类:
///<summary> ///任务处理帮助类 ///</summary> publicclassQuartzHelper { publicQuartzHelper(){} publicQuartzHelper(stringquartzServer,stringquartzPort) { Server=quartzServer; Port=quartzPort; } ///<summary> ///锁对象 ///</summary> privatestaticreadonlyobjectObj=newobject(); ///<summary> ///方案 ///</summary> privateconststringScheme="tcp"; ///<summary> ///服务器的地址 ///</summary> publicstaticstringServer{get;set;} ///<summary> ///服务器的端口 ///</summary> publicstaticstringPort{get;set;} ///<summary> ///缓存任务所在程序集信息 ///</summary> privatestaticreadonlyDictionary<string,Assembly>AssemblyDict=newDictionary<string,Assembly>(); ///<summary> ///程序调度 ///</summary> privatestaticIScheduler_scheduler; ///<summary> ///初始化任务调度对象 ///</summary> publicstaticvoidInitScheduler() { try { lock(Obj) { if(_scheduler!=null)return; //配置文件的方式,配置quartz实例 ISchedulerFactoryschedulerFactory=newStdSchedulerFactory(); _scheduler=schedulerFactory.GetScheduler(); } } catch(Exceptionex) { thrownewException(ex.Message); } } ///<summary> ///启用任务调度 ///启动调度时会把任务表中状态为“执行中”的任务加入到任务调度队列中 ///</summary> publicstaticvoidStartScheduler() { try { if(_scheduler.IsStarted)return; //添加全局监听 _scheduler.ListenerManager.AddTriggerListener(newCustomTriggerListener(),GroupMatcher<TriggerKey>.AnyGroup()); _scheduler.Start(); //获取所有执行中的任务 List<TaskModel>listTask=TaskHelper.GetAllTaskList().ToList(); if(listTask.Count>0) { foreach(TaskModeltaskUtilinlistTask) { try { ScheduleJob(taskUtil); } catch(Exceptione) { thrownewException(taskUtil.TaskName,e); } } } } catch(Exceptionex) { thrownewException(ex.Message); } } ///<summary> ///启用任务 ///<paramname="task">任务信息</param> ///<paramname="isDeleteOldTask">是否删除原有任务</param> ///<returns>返回任务trigger</returns> ///</summary> publicstaticvoidScheduleJob(TaskModeltask,boolisDeleteOldTask=false) { if(isDeleteOldTask) { //先删除现有已存在任务 DeleteJob(task.TaskID.ToString()); } //验证是否正确的Cron表达式 if(ValidExpression(task.CronExpressionString)) { IJobDetailjob=newJobDetailImpl(task.TaskID.ToString(),GetClassInfo(task.AssemblyName,task.ClassName)); //添加任务执行参数 job.JobDataMap.Add("TaskParam",task.TaskParam); CronTriggerImpltrigger=newCronTriggerImpl { CronExpressionString=task.CronExpressionString, Name=task.TaskID.ToString(), Description=task.TaskName }; _scheduler.ScheduleJob(job,trigger); if(task.Status==TaskStatus.STOP) { JobKeyjk=newJobKey(task.TaskID.ToString()); _scheduler.PauseJob(jk); } else { List<DateTime>list=GetNextFireTime(task.CronExpressionString,5); foreach(vartimeinlist) { LogHelper.WriteLog(time.ToString(CultureInfo.InvariantCulture)); } } } else { thrownewException(task.CronExpressionString+"不是正确的Cron表达式,无法启动该任务!"); } } ///<summary> ///初始化远程Quartz服务器中的,各个Scheduler实例。 ///提供给远程管理端的后台,用户获取Scheduler实例的信息。 ///</summary> publicstaticvoidInitRemoteScheduler() { try { NameValueCollectionproperties=newNameValueCollection { ["quartz.scheduler.instanceName"]="ExampleQuartzScheduler", ["quartz.scheduler.proxy"]="true", ["quartz.scheduler.proxy.address"]=string.Format("{0}://{1}:{2}/QuartzScheduler",Scheme,Server,Port) }; ISchedulerFactorysf=newStdSchedulerFactory(properties); _scheduler=sf.GetScheduler(); } catch(Exceptionex) { thrownewException(ex.StackTrace); } } ///<summary> ///删除现有任务 ///</summary> ///<paramname="jobKey"></param> publicstaticvoidDeleteJob(stringjobKey) { try { JobKeyjk=newJobKey(jobKey); if(_scheduler.CheckExists(jk)) { //任务已经存在则删除 _scheduler.DeleteJob(jk); } } catch(Exceptionex) { thrownewException(ex.Message); } } ///<summary> ///暂停任务 ///</summary> ///<paramname="jobKey"></param> publicstaticvoidPauseJob(stringjobKey) { try { JobKeyjk=newJobKey(jobKey); if(_scheduler.CheckExists(jk)) { //任务已经存在则暂停任务 _scheduler.PauseJob(jk); } } catch(Exceptionex) { thrownewException(ex.Message); } } ///<summary> ///恢复运行暂停的任务 ///</summary> ///<paramname="jobKey">任务key</param> publicstaticvoidResumeJob(stringjobKey) { try { JobKeyjk=newJobKey(jobKey); if(_scheduler.CheckExists(jk)) { //任务已经存在则暂停任务 _scheduler.ResumeJob(jk); } } catch(Exceptionex) { thrownewException(ex.Message); } } ///<summary> ///获取类的属性、方法 ///</summary> ///<paramname="assemblyName">程序集</param> ///<paramname="className">类名</param> privatestaticTypeGetClassInfo(stringassemblyName,stringclassName) { try { assemblyName=FileHelper.GetAbsolutePath(assemblyName+".dll"); Assemblyassembly=null; if(!AssemblyDict.TryGetValue(assemblyName,outassembly)) { assembly=Assembly.LoadFrom(assemblyName); AssemblyDict[assemblyName]=assembly; } Typetype=assembly.GetType(className,true,true); returntype; } catch(Exceptionex) { thrownewException(ex.Message); } } ///<summary> ///停止任务调度 ///</summary> publicstaticvoidStopSchedule() { try { //判断调度是否已经关闭 if(!_scheduler.IsShutdown) { //等待任务运行完成 _scheduler.Shutdown(true); } } catch(Exceptionex) { thrownewException(ex.Message); } } ///<summary> ///校验字符串是否为正确的Cron表达式 ///</summary> ///<paramname="cronExpression">带校验表达式</param> ///<returns></returns> publicstaticboolValidExpression(stringcronExpression) { returnCronExpression.IsValidExpression(cronExpression); } ///<summary> ///获取任务在未来周期内哪些时间会运行 ///</summary> ///<paramname="CronExpressionString">Cron表达式</param> ///<paramname="numTimes">运行次数</param> ///<returns>运行时间段</returns> publicstaticList<DateTime>GetNextFireTime(stringCronExpressionString,intnumTimes) { if(numTimes<0) { thrownewException("参数numTimes值大于等于0"); } //时间表达式 ITriggertrigger=TriggerBuilder.Create().WithCronSchedule(CronExpressionString).Build(); IList<DateTimeOffset>dates=TriggerUtils.ComputeFireTimes(triggerasIOperableTrigger,null,numTimes); List<DateTime>list=newList<DateTime>(); foreach(DateTimeOffsetdtfindates) { list.Add(TimeZoneInfo.ConvertTimeFromUtc(dtf.DateTime,TimeZoneInfo.Local)); } returnlist; } publicstaticobjectCurrentTaskList() { thrownewNotImplementedException(); } ///<summary> ///获取当前执行的Task对象 ///</summary> ///<paramname="context"></param> ///<returns></returns> publicstaticTaskModelGetTaskDetail(IJobExecutionContextcontext) { TaskModeltask=newTaskModel(); if(context!=null) { task.TaskID=Guid.Parse(context.Trigger.Key.Name); task.TaskName=context.Trigger.Description; task.RecentRunTime=DateTime.Now; task.TaskParam=context.JobDetail.JobDataMap.Get("TaskParam")!=null?context.JobDetail.JobDataMap.Get("TaskParam").ToString():""; } returntask; } }
2.设置执行中的任务:
publicclassTaskBll { privatereadonlyTaskDAL_dal=newTaskDAL(); ///<summary> ///获取任务列表 ///</summary> ///<paramname="pageIndex"></param> ///<paramname="pageSize"></param> ///<returns></returns> publicPageOf<TaskModel>GetTaskList(intpageIndex,intpageSize) { return_dal.GetTaskList(pageIndex,pageSize); } ///<summary> ///读取数据库中全部的任务 ///</summary> ///<returns></returns> publicList<TaskModel>GetAllTaskList() { return_dal.GetAllTaskList(); } ///<summary> /// ///</summary> ///<paramname="taskId"></param> ///<returns></returns> publicTaskModelGetById(stringtaskId) { thrownewNotImplementedException(); } ///<summary> ///删除任务 ///</summary> ///<paramname="taskId"></param> ///<returns></returns> publicboolDeleteById(stringtaskId) { return_dal.UpdateTaskStatus(taskId,-1); } ///<summary> ///修改任务 ///</summary> ///<paramname="taskId"></param> ///<paramname="status"></param> ///<returns></returns> publicboolUpdateTaskStatus(stringtaskId,intstatus) { return_dal.UpdateTaskStatus(taskId,status); } ///<summary> ///修改任务的下次启动时间 ///</summary> ///<paramname="taskId"></param> ///<paramname="nextFireTime"></param> ///<returns></returns> publicboolUpdateNextFireTime(stringtaskId,DateTimenextFireTime) { return_dal.UpdateNextFireTime(taskId,nextFireTime); } ///<summary> ///根据任务Id修改上次运行时间 ///</summary> ///<paramname="taskId"></param> ///<paramname="recentRunTime"></param> ///<returns></returns> publicboolUpdateRecentRunTime(stringtaskId,DateTimerecentRunTime) { return_dal.UpdateRecentRunTime(taskId,recentRunTime); } ///<summary> ///根据任务Id获取任务 ///</summary> ///<paramname="taskId"></param> ///<returns></returns> publicTaskModelGetTaskById(stringtaskId) { return_dal.GetTaskById(taskId); } ///<summary> ///添加任务 ///</summary> ///<paramname="task"></param> ///<returns></returns> publicboolAdd(TaskModeltask) { return_dal.Add(task); } ///<summary> ///修改任务 ///</summary> ///<paramname="task"></param> ///<returns></returns> publicboolEdit(TaskModeltask) { return_dal.Edit(task); } }
3.任务实体:
///<summary> ///任务实体 ///</summary> publicclassTaskModel { ///<summary> ///任务ID ///</summary> publicGuidTaskID{get;set;} ///<summary> ///任务名称 ///</summary> publicstringTaskName{get;set;} ///<summary> ///任务执行参数 ///</summary> publicstringTaskParam{get;set;} ///<summary> ///运行频率设置 ///</summary> publicstringCronExpressionString{get;set;} ///<summary> ///任务运频率中文说明 ///</summary> publicstringCronRemark{get;set;} ///<summary> ///任务所在DLL对应的程序集名称 ///</summary> publicstringAssemblyName{get;set;} ///<summary> ///任务所在类 ///</summary> publicstringClassName{get;set;} publicTaskStatusStatus{get;set;} ///<summary> ///任务创建时间 ///</summary> publicDateTime?CreatedTime{get;set;} ///<summary> ///任务修改时间 ///</summary> publicDateTime?ModifyTime{get;set;} ///<summary> ///任务最近运行时间 ///</summary> publicDateTime?RecentRunTime{get;set;} ///<summary> ///任务下次运行时间 ///</summary> publicDateTime?NextFireTime{get;set;} ///<summary> ///任务备注 ///</summary> publicstringRemark{get;set;} ///<summary> ///是否删除 ///</summary> publicintIsDelete{get;set;} }
4.配置文件:
#Youcanconfigureyourschedulerineither<quartz>configurationsection #orinquartzpropertiesfile #Configurationsectionhasprecedence quartz.scheduler.instanceName=ExampleQuartzScheduler #configurethreadpoolinfo quartz.threadPool.type=Quartz.Simpl.SimpleThreadPool,Quartz quartz.threadPool.threadCount=10 quartz.threadPool.threadPriority=Normal #jobinitializationpluginhandlesourxmlreading,withoutitdefaultsareused #quartz.plugin.xml.type=Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin,Quartz #quartz.plugin.xml.fileNames=~/quartz_jobs.xml #exportthisservertoremotingcontext 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
四.总结:
在项目中比较多的使用到定时任务的功能,今天的介绍的组件可以很好的完成一些定时任务的要求。这篇文章主要是作为引子,简单的介绍了组件的背景和组件的使用方式,如果项目中需要使用,可以进行更加深入的了解。