一个任务调度
Posted 逸飛
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一个任务调度相关的知识,希望对你有一定的参考价值。
最近把以前项目中用的任务调度提了出来,做了一个Demo。
任务调度用到的组件是quartz.net。关于quartz.net的文章网上有很多了,这里再简单介绍下。
首先是创建一个作业明细
/// <summary> /// 根据作业计划来创建作业明细 /// </summary> /// <param name="task"></param> /// <param name="taskData"></param> /// <returns></returns> public static IJobDetail CreateJobDetail(ScheduleTask task, IDictionary<string, object> taskData = null) { if (task == null) throw new ArgumentNullException("『CreateJobDetail』的task参数为空!"); //反射加载程序集 var path = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, string.Format("bin\\\\TaskManageDemo.Task.dll")); Assembly asmb = Assembly.LoadFrom(path); Type taskType = asmb.GetType(string.Format("TaskManageDemo.Task.{0}", task.MethodName)); if (taskType == null) throw new NotImplementedException(string.Format("『{0}』调用的类型未实现", task.MethodName)); //作业执行上下文携带数据 IDictionary<string, object> map = new Dictionary<string, object>() { { "task", task } }; IJobDetail job = JobBuilder.Create(taskType) .WithDescription(task.Remark) .WithIdentity(GetJobKey(task)) .UsingJobData(new JobDataMap(map)) .Build(); return job; }
然后是创建一个触发器
/// <summary> /// 根据作业计划来创建作业触发器 /// </summary> /// <param name="task"></param> /// <param name="forJob"></param> /// <returns></returns> public static ITrigger CreateTrigger(ScheduleTask task, IJobDetail forJob = null) { if (task == null) throw new ArgumentNullException("『CreateTrigger』的task参数为空!"); TriggerBuilder trigger = TriggerBuilder.Create(); trigger.WithDescription(task.Remark) .WithIdentity(GetTriggerKey(task)); trigger = string.IsNullOrEmpty(task.CronExpression) ? trigger.WithSimpleSchedule(x => x.WithIntervalInMinutes(30).WithRepeatCount(10)) : trigger.WithCronSchedule(task.CronExpression); if (forJob != null) trigger.ForJob(forJob); return trigger.Build(); }
最后把创建的两样组合起来就新增了一个作业了
/// <summary> /// 使用作业计划来创建作业 /// </summary> /// <param name="quartzScheduler">调度器</param> /// <param name="task">作业任务</param> /// <returns>二元组</returns> public static Tuple<IJobDetail, ITrigger> CreatScheduleJob(IScheduler quartzScheduler, ScheduleTask task) { Tuple<IJobDetail, ITrigger> tuple = null; IJobDetail ij = CreateJobDetail(task); ITrigger it = CreateTrigger(task); quartzScheduler.ScheduleJob(ij, it); tuple = new Tuple<IJobDetail, ITrigger>(ij, it); return tuple; }
作业必须实现IJob接口
这里用了一个基类来实现IJob接口,基类里有一个抽象方法,其他作业子类只要继承这个基类并实现这个抽象方法就行了。
public void Execute(IJobExecutionContext context) { //Quartz.Collection.ISet<JobKey> jobKeys = context.Scheduler.GetJobKeys( // Quartz.Impl.Matchers.GroupMatcher<JobKey>.GroupEquals(JobHelp.jobGroupName)); //取所有运行的作业 var task = context.MergedJobDataMap["task"] as ScheduleTask; string message = string.Format("{0}的『Execute』从『IJobExecutionContext』读取不到作业计划!", ""); // this.FullDevName if (task == null) throw new Exception(message); //刷新作业计划信息,防止作业计划配置发生改变 string sql = "select * from ScheduleTask where id=@id"; var taskNew = _scheduleTask.GetFirst<ScheduleTask>(sql, new { id = task.Id }); if (taskNew == null) { //计划已经被删除,则删除此作业 context.Scheduler.DeleteJob(context.JobDetail.Key); Log.Logger.InfoFormat(string.Format("{0}作业计划为空,该记录可能已经被删除。", taskNew.MethodName)); return; //退出 } //作业不允许使用 if (!taskNew.Allowused) { //不从调度计划中删除本作业,因为有可能又启用该作业计划 Log.Logger.InfoFormat(string.Format("{0}作业计划不允许使用,跳过此次执行。", taskNew.MethodName)); return; //退出 } if (taskNew != task) { //脏数据,删除此作业,然后重新创建一个 Log.Logger.InfoFormat("{0}的作业计划属性已更改,将删除该计划的实现作业,然后重新创建一个作业,并尝试调度它...", taskNew.MethodName); //作业计划属性发生变更,重新启动作业 Tuple<IJobDetail, ITrigger> tuple = JobHelp.RestartJob(context.Scheduler, task, taskNew); Log.Logger.InfoFormat("{0}重新创建并调度作业完成,『IJOB.Execute』退出。作业计划:{1},作业:{2},触发器:{3},表达式:{4}。", taskNew.MethodName, taskNew.MethodName, tuple.Item1.Key.Name, tuple.Item2.Key.Name, taskNew.CronExpression); return; //退出 } //执行具体作业的业务逻辑 ExecuteJobImpl(context); //更新执行时间 taskNew.LastTime=DateTime.Now.ToString(); taskNew.NextTime = string.Format("{0:G}", context.NextFireTimeUtc.Value.AddHours(8)); UpdateTime(taskNew); }
新增作业
怎样新增一个作业呢?
新增一个作业也很简单,只需在TaskManageDemo.Task项目下添加一个类,并继承作业父类实现抽象方法就可以了。
那怎样来配置呢?
实现了作业后,点击新增按钮来增加一个作业。
新增的作业是怎样被触发的呢?
这里我用了一个系统作业来唤醒新增的作业和删除不需要的作业。
protected override void ExecuteJobImpl(Quartz.IJobExecutionContext context) { //取所有运行的作业 Quartz.Collection.ISet<JobKey> jobKeys = context.Scheduler.GetJobKeys( Quartz.Impl.Matchers.GroupMatcher<JobKey>.GroupEquals(JobHelp.jobGroupName)); string sql = "select * from ScheduleTask where ClassName!=@ClassName"; var taskRuning = new List<ScheduleTask>();//正在运行的作业 var taskInDb = _scheduleTask.Query<ScheduleTask>(sql, new { ClassName = "SysJob" }); //存在于数据库的作业,不包括系统作业 foreach (var jk in jobKeys) { IJobDetail job = context.Scheduler.GetJobDetail(jk); ScheduleTask task = job.JobDataMap["task"] as ScheduleTask; if (task == null || task.ClassName == "SysJob") continue; //不检查系统作业 //在数据库检测一次 var taskInDb2 = taskInDb.ToList().FirstOrDefault(a => a.Id == task.Id); if (taskInDb2 == null) { context.Scheduler.DeleteJob(jk); //删除该作业 Log.Logger.InfoFormat("作业计划『{0}』已经不存在于数据库。", task.ClassName); continue; } taskRuning.Add(taskInDb2); if (taskInDb2 != task) { //脏数据,删除此作业,然后重新创建一个 Log.Logger.InfoFormat("{0}的作业计划属性已更改,将删除该计划的实现作业,然后重新创建一个作业,并尝试调度它...", taskInDb2.ClassName); //作业计划属性发生变更,重新启动作业 Tuple<IJobDetail, ITrigger> tuple = JobHelp.RestartJob(context.Scheduler, task, taskInDb2); Log.Logger.InfoFormat("{0}重新创建并调度作业完成,『IJOB.Execute』退出。作业计划:{1},作业:{2},触发器:{3},表达式:{4}。", taskInDb2.ClassName, taskInDb2.ClassName, tuple.Item1.Key.Name, tuple.Item2.Key.Name, taskInDb2.CronExpression); return; //退出 } } //过滤出新增的作业 var newTask = taskInDb.Except(taskRuning); if (newTask.Count() > 0) { //动态增加作业 Log.Logger.InfoFormat("系统作业检测到有{0}个新增作业计划,开始创建这些作业...", newTask.Count()); foreach (var task in newTask) TaskManageDemo.Utility.Common.Execute(ScheduleJobByPlan, context.Scheduler, task, "创建作业失败,作业计划名称:{0}", task.ClassName); Log.Logger.InfoFormat("系统作业创建作业完毕,共创建{0}个作业。", context.Scheduler.GetJobKeys(Quartz.Impl.Matchers.GroupMatcher<JobKey>.GroupEquals(JobHelp.jobGroupName)).Count - 1); } //系统作业执行完毕 Log.Logger.InfoFormat("系统轮询作业执行完毕,目前共{0}个作业正在运行。", context.Scheduler.GetJobKeys(Quartz.Impl.Matchers.GroupMatcher<JobKey>.GroupEquals(JobHelp.jobGroupName)).Count); context.Put("ExecResult", "系统作业执行完成。"); //Log.Logger.ErrorFormat("本次运行时间{0},下次运行时间{1}", DateTime.Now.ToString(), context.NextFireTimeUtc.Value.DateTime.AddHours(8).ToString()); }
主要的应该也就这些,当然这是个Demo,有很多地方不是很完善。
源码下载
https://git.oschina.net/bin88123/TaskManageDemo
以上是关于一个任务调度的主要内容,如果未能解决你的问题,请参考以下文章
用于调度代码重复运行的 Windows 任务调度程序有多可靠?