详解免费开源的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
四.总结:
在项目中比较多的使用到定时任务的功能,今天的介绍的组件可以很好的完成一些定时任务的要求。这篇文章主要是作为引子,简单的介绍了组件的背景和组件的使用方式,如果项目中需要使用,可以进行更加深入的了解。