你的位置:首页 > ASP.net教程

[ASP.net教程]Quartz.NET开源作业调度框架系列(五):AdoJobStore保存job到数据库


  Quartz.NET 任务调度的核心元素是 scheduler, trigger 和 job,其中 trigger(用于定义调度时间的元素,即按照什么时间规则去执行任务) 和 job 是任务调度的元数据,scheduler 是实际执行调度的控制器。在Quartz.NET中主要有两种类型的 job:无状态的(stateless)和有状态的(stateful)。对于同一个 trigger 来说,有状态的 job 不能被并行执行,只有上一次触发的任务被执行完之后,才能触发下一次执行。无状态任务一般指可以并发的任务,即任务之间是独立的,不会互相干扰。一个 job 可以被多个 trigger 关联,但是一个 trigger 只能关联一个 job。某些任务需要对数据库中的数据进行增删改处理 , 这些任务不能并发执行,就需要用到无状态的任务 , 否则会造成数据混乱。

  另外有些情况下,我们需要将任务保存到数据库中,特别是有些任务中包含参数,例如累加的任务,如果可以保存到数据库中,即便中间断电或者程序异常重启,中间计算的结果也不会丢失,可以从断点的结果进行运算(首先恢复任务),下面介绍一下如何用AdoJobStore将任务保存到SQL Server数据库中. 

  事先要在数据库上新建一个QRTZ_数据库,并执行SQL建表脚本:

1 RecoveryJob

  是一个无状态的任务,代码如下:

 1 using System; 2 using System.Collections.Specialized; 3 using System.Threading; 4 using Common.Logging; 5 using Quartz; 6 using Quartz.Impl; 7 using Quartz.Job; 8 using System.Windows.Forms; 9 namespace QuartzDemo10 {11   /// <summary>12   /// 无状态的可恢复的任务13   /// </summary>14   public class RecoveryJob : IJob15   {16    17     private const string Count = "count";18     public virtual void Execute(IJobExecutionContext context)19     {20 21       JobKey jobKey = context.JobDetail.Key;22       if (isOpen("FrmConsole"))23       {24         try25         {26           //获取当前Form1实例27           __instance = (FrmConsole)Application.OpenForms["FrmConsole"];28           // 如果任务是恢复的任务的话29           if (context.Recovering)30           {31             __instance.SetInfo(string.Format("{0} RECOVERING at {1}", jobKey, DateTime.Now.ToString("r")));32           }33           else34           {35             __instance.SetInfo(string.Format("{0} starting at {1}", jobKey, DateTime.Now.ToString("r")));36           }37 38           JobDataMap data = context.JobDetail.JobDataMap;39           int count;40           if (data.ContainsKey(Count))41           {42             //是否能从数据库中恢复,如果保存Job等信息的话,程序运行突然终端(可用调试时中断运行,而不是关闭窗体来模拟)43             count = data.GetInt(Count);44           }45           else46           {47             count = 0;48           }49           count++;50           data.Put(Count, count);51 52           __instance.SetInfo(string.Format(" {0} Count #{1}", jobKey, count));53         }54         catch (Exception ex)55         {56           Console.WriteLine(ex.Message);57         }58       }59     }60 61 62     private static FrmConsole __instance = null;63 64     /// <summary>65     /// 判断窗体是否打开66     /// </summary>67     /// <param name="appName"></param>68     /// <returns></returns>69     private bool isOpen(string appName)70     {71       FormCollection collection = Application.OpenForms;72       foreach (Form form in collection)73       {74         if (form.Name == appName)75         {76           return true;77         }78       }79       return false;80     }81 82   }83 }

2 RecoveryStatefulJob

  是一个有状态的任务,和无状态的区别就是在任务类的上面用[PersistJobDataAfterExecution]标注任务是有状态的 , 有状态的任务不允许并发执行,也需要标注 [DisallowConcurrentExecution],代码如下:

 1 using System; 2 using System.Collections.Specialized; 3 using System.Threading; 4 using Common.Logging; 5 using Quartz; 6 using Quartz.Impl; 7 using Quartz.Job; 8 using System.Windows.Forms; 9 namespace QuartzDemo10 {11   /// <summary>12   /// 用这个[PersistJobDataAfterExecution]标注任务是有状态的,13   /// 有状态的任务不允许并发执行 [DisallowConcurrentExecution]14   /// </summary>15   [PersistJobDataAfterExecution]16   [DisallowConcurrentExecution]17   public class RecoveryStatefulJob : RecoveryJob18   {19 20   }21 }

3 AdoJobStoreExample

   用 properties["quartz.dataSource.default.connectionString"] = "Server=(local);Database=QRTZ_;Trusted_Connection=True;";定义了数据库的连接信息,程序运行时会自动将任务保存到数据库中:

 1 using System; 2 using System.Collections.Specialized; 3 using System.Threading; 4 using Common.Logging; 5 using Quartz; 6 using Quartz.Impl; 7 using Quartz.Job; 8 using System.Windows.Forms; 9 namespace QuartzDemo 10 { 11   /// <summary>  12   /// AdoJobStore的用法示例 13   /// </summary> 14   public class AdoJobStoreExample  15   { 16     public virtual void Run(bool inClearJobs, bool inScheduleJobs) 17     { 18       NameValueCollection properties = new NameValueCollection(); 19  20       properties["quartz.scheduler.instanceName"] = "TestScheduler"; 21       properties["quartz.scheduler.instanceId"] = "instance_one"; 22       properties["quartz.threadPool.type"] = "Quartz.Simpl.SimpleThreadPool, Quartz"; 23       properties["quartz.threadPool.threadCount"] = "5"; 24       properties["quartz.threadPool.threadPriority"] = "Normal"; 25       properties["quartz.jobStore.misfireThreshold"] = "60000"; 26       properties["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz"; 27       properties["quartz.jobStore.useProperties"] = "false"; 28       properties["quartz.jobStore.dataSource"] = "default"; 29       properties["quartz.jobStore.tablePrefix"] = "QRTZ_"; 30       properties["quartz.jobStore.clustered"] = "true"; 31       // SQLite 32       // properties["quartz.jobStore.lockHandler.type"] = "Quartz.Impl.AdoJobStore.UpdateLockRowSemaphore, Quartz"; 33       properties["quartz.jobStore.driverDelegateType"] = "Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz"; 34     // 数据库连接字符串 35       properties["quartz.dataSource.default.connectionString"] = "Server=(local);Database=QRTZ_;Trusted_Connection=True;"; 36       properties["quartz.dataSource.default.provider"] = "SqlServer-20"; 37  38       // First we must get a reference to a scheduler 39       ISchedulerFactory sf = new StdSchedulerFactory(properties); 40       IScheduler sched = sf.GetScheduler(); 41  42       bool b是否恢复 = false; 43       if (inClearJobs) 44       { 45        Console.WriteLine("***** Deleting existing jobs/triggers *****"); 46        // sched.Clear(); 47       } 48  49      50       if (inScheduleJobs) 51       { 52        53         string schedId = sched.SchedulerInstanceId; 54  55         int count = 1; 56  57         //定义一个无状态的任务 58         IJobDetail job = JobBuilder.Create<RecoveryJob>() 59           .WithIdentity("recoveryjob_" + count, schedId)  60           .RequestRecovery() //recovery 61           .Build(); 62  63  64         ISimpleTrigger trigger = (ISimpleTrigger)TriggerBuilder.Create() 65                                .WithIdentity("triger_" + count, schedId) 66                                .StartAt(DateBuilder.FutureDate(1, IntervalUnit.Second)) 67                                .WithSimpleSchedule(x => x.WithRepeatCount(20).WithInterval(TimeSpan.FromSeconds(3))) 68                                .Build(); 69         //可用此来查看定义的触发器触发规则 70         //log.InfoFormat("{0} will run at: {1} and repeat: {2} times, every {3} seconds",  71         //job.Key, trigger.GetNextFireTimeUtc(),  72         //trigger.RepeatCount,  73         //trigger.RepeatInterval.TotalSeconds); 74         try 75         { 76           //如果数据库已经存在同名job和trigger,则绑定失败 77           sched.ScheduleJob(job, trigger); 78         } 79         catch 80         { 81           b是否恢复 = true; 82         } 83         count++; 84  85         //定义一个有状态的任务*********************************************************** 86         job = JobBuilder.Create<RecoveryStatefulJob>() 87           .WithIdentity("Statefuljob_" + count, schedId) 88           .RequestRecovery() // recovery 89           .Build(); 90  91         trigger = (ISimpleTrigger)TriggerBuilder.Create() 92                        .WithIdentity("triger_" + count, schedId) 93                        .StartAt(DateBuilder.FutureDate(1, IntervalUnit.Second)) 94                        .WithSimpleSchedule(x => x.WithRepeatCount(20).WithInterval(TimeSpan.FromSeconds(3))) 95                         .Build(); 96       97         try 98         { 99           sched.ScheduleJob(job, trigger);100         }101         catch102         {103           b是否恢复 = true;104         }105 106 107        108       }109     110       //启动111       sched.Start();112       //sched.Shutdown();113 114     }115 116     public string Name117     {118       get { return GetType().Name; }119     }120 121     public void Run()122     {123       bool clearJobs = true;124       //clearJobs = false;125       bool scheduleJobs = true;126       AdoJobStoreExample example = new AdoJobStoreExample();127       example.Run(clearJobs, scheduleJobs);128     }129   }130 }

 

  可以看到有状态的计数每次累加1,而无状态的每次执行时都会丢失累加数(新的实例),中断程序,查看数据库的QRTZ_JOB_DETAILS表,可以看见还有一个持久化的任务:

  中断程序后(调试状态时不关闭窗体,而是中断调试,模拟异常关闭) ,再重新运行可以看到如下界面: