Quartz.net的使用——显示任务信息、暂停、恢复、显示下次触发时间、禁止并发运行

Quartz.net的使用——显示任务信息、暂停、恢复、显示下次触发时间、禁止并发运行

雨欲语

        最近在使用Quartz.net开发项目的时候,因为业务需求,我需要在页面进行任务的管理,比如暂停、恢复、显示下次执行时间等。网上关于C#中的Quartz的信息很少,因此我很多是通过查找Java的API完成的,如果大家需要其它需求,也可以直接查找Java中Quartz怎么使用的就行。

        这里我使用的是基于配置文件的方式进行的,对于Quartz.net不熟悉的可以看我的其它博客,这里我就不多讲了,直接开始上代码。

        我这里建立一个Windows窗体应用,窗体类命名为:MainView。

        首先建立两个Job类,其中的MainView mainView = MainView.GetInstance();mainView.JobList(); 先不用管,之后我会解释为什么用这个,我这里每10秒触发一次Job,然后Job1执行至少需要15秒,为了让其不并发执行,加入了[DisallowConcurrentExecutionAttribute]属性,我理解这东西类似于Java中的注解:

 
  1. [DisallowConcurrentExecutionAttribute]

  2. public class Job1:IJob

  3. {

  4. public void Execute(IJobExecutionContext context)

  5. {

  6. MainView mainView = MainView.GetInstance();

  7. mainView.JobList();

  8. //模拟运行该任务需要15秒

  9. Thread.Sleep(15000);

  10. MessageBox.Show("第一个Job:" + DateTime.Now.ToString());

  11. }

  12. }

 
  1. public class Job2:IJob

  2. {

  3. public void Execute(IJobExecutionContext context)

  4. {

  5. MainView mainView = MainView.GetInstance();

  6. mainView.JobList();

  7. MessageBox.Show("第二个Job:" + DateTime.Now.ToString());

  8. }

  9. }

        接着编写Quartz的配置文件quartz_jobs.xml,每10秒运行一次:

 
  1. <job-scheduling-data xmlns="http://quartznet.sourceforge.net/JobSchedulingData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0">

  2.  
  3. <processing-directives>

  4. <overwrite-existing-data>true</overwrite-existing-data>

  5. </processing-directives>

  6.  
  7. <schedule>

  8.  
  9. <job>

  10. <!--作业名字,随便取-->

  11. <name>Job1</name>

  12. <!--组名,随便取-->

  13. <group>group1</group>

  14. <!--描述-->

  15. <description>job1</description>

  16. <!--作业类,要定位到我们代码中创建的作业,命名空间.类名-->

  17. <job-type>QuartzTest.Job1, QuartzTest</job-type>

  18. <durable>true</durable>

  19. <recover>false</recover>

  20.  
  21. </job>

  22.  
  23. <trigger>

  24. <!--cron表达式触发器-->

  25. <cron>

  26. <!--触发器名字,随便取-->

  27. <name>Trigger1</name>

  28. <!--组名-->

  29. <group>group1</group>

  30. <!--描述-->

  31. <description>trigger</description>

  32. <!--作业名,触发哪个作业-->

  33. <job-name>Job1</job-name>

  34. <!--作业的组名,与作业对应-->

  35. <job-group>group1</job-group>

  36. <misfire-instruction>DoNothing</misfire-instruction>

  37. <cron-expression>0/10 * * * * ?</cron-expression>

  38. </cron>

  39. </trigger>

  40.  
  41. <job>

  42. <!--作业名字,随便取-->

  43. <name>Job2</name>

  44. <!--组名,随便取-->

  45. <group>group2</group>

  46. <!--描述-->

  47. <description>job2</description>

  48. <!--作业类,要定位到我们代码中创建的作业,命名空间.类名-->

  49. <job-type>QuartzTest.Job2, QuartzTest</job-type>

  50. <durable>true</durable>

  51. <recover>false</recover>

  52.  
  53. </job>

  54.  
  55. <trigger>

  56. <!--cron表达式触发器-->

  57. <cron>

  58. <!--触发器名字,随便取-->

  59. <name>Trigger2</name>

  60. <!--组名-->

  61. <group>group2</group>

  62. <!--描述-->

  63. <description>trigger</description>

  64. <!--作业名,触发哪个作业-->

  65. <job-name>Job2</job-name>

  66. <!--作业的组名,与作业对应-->

  67. <job-group>group2</job-group>

  68. <misfire-instruction>DoNothing</misfire-instruction>

  69. <cron-expression>0/10 * * * * ?</cron-expression>

  70. </cron>

  71. </trigger>

  72.  
  73.  
  74. </schedule>

  75. </job-scheduling-data>

        接着需要配置程序的配置文件,即App.config

 
  1. <?xml version="1.0" encoding="utf-8" ?>

  2. <configuration>

  3. <configSections>

  4. <section name="quartz" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089" />

  5.  
  6. </configSections>

  7. <quartz>

  8. <add key="quartz.scheduler.instanceName" value="ServerScheduler" />

  9.  
  10. <add key="quartz.threadPool.type" value="Quartz.Simpl.SimpleThreadPool, Quartz" />

  11. <add key="quartz.threadPool.threadCount" value="10" />

  12. <add key="quartz.threadPool.threadPriority" value="2" />

  13.  
  14. <add key="quartz.plugin.xml.type" value = "Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin, Quartz" />

  15. <add key="quartz.plugin.xml.fileNames" value = "quartz_jobs.xml" />

  16.  
  17. <add key="quartz.jobStore.misfireThreshold" value="60000" />

  18. <add key="quartz.jobStore.type" value="Quartz.Simpl.RAMJobStore, Quartz" />

  19. </quartz>

  20. </configuration>

        这里为了方便,我建立了一个QuartzManage类,里面放了我们需要用到的一些属性:

 
  1. public class QuartzManage

  2. {

  3. public static string jobName = "";

  4. public static string jobGroupName = "";

  5. public static string triggerName = "";

  6. public static string triggerGroupName = "";

  7.  
  8. //创建一个调度工厂

  9. public static IScheduler scheduler = GetScheduler();

  10.  
  11. private static IScheduler GetScheduler()

  12. {

  13. StdSchedulerFactory stdSchedulerFactory = new StdSchedulerFactory();

  14. IScheduler scheduler = stdSchedulerFactory.GetScheduler();

  15. return scheduler;

  16. }

  17. }

       界面设置如下:

        Quartz.net的使用——显示任务信息、暂停、恢复、显示下次触发时间、禁止并发运行

        这里界面我加入了单例模式,待会解释为什么使用单例模式,使用单例,记得在项目的Program.cs中将Application.Run(New MainView())修改为Application.Run(MainView.GetInstance()):

 
  1. private static MainView mainView = new MainView();

  2.  
  3. public static MainView GetInstance()

  4. {

  5. return mainView;

  6. }

        在启动了项目后,应该要启动这些定时任务,并且要将这些任务的信息给显示出来,因此在主窗体加载的时候,需要开启任务,并且将任务信息放入JobDataGridView中:

 
  1. private void MainView_Load(object sender, EvventArgs e)

  2. {

  3. QuartzManage.scheduler.Start();

  4. JobList();

  5. }

        这里的JobList()是我单独封装的将信息显示在JobDataGridView中的方法:

 
  1. public void JobList()

  2. {

  3. //此方法获取所有的Job的对象

  4. IList<IJobExecutionContext> jobs = QuartzManage.scheduler.GetCurrentlyExecutingJobs();

  5. //此方法获取所有的Job的group的名字

  6. IList<string> name = QuartzManage.scheduler.GetJobGroupNames();

  7. for(int i = 0;i < name.Count;i++)

  8. {

  9. //给jobDataGridView的行赋值,表明多少行

  10. this.jobDataGridView.RowCount = name.Count;

  11. //GetJobKeys获取到所有Job的JobKey,相当于Job信息

  12. foreach(JobKey jobKey in QuartzManage.scheduler.GetJobKeys(GroupMatcher<JobKey>.GroupEquals(name[i])))

  13. {

  14. this.jobDataGridView.Rows[i].Cells[0].Value = jobKey.Name;

  15. this.jobDataGridView.Rows[i].Cells[1].Value = jobKey.Group;

  16. }

  17. //GetTriggerKeys获取到所有Trigger的TriggerKey,相当于Trigger信息

  18. foreach(TriggerKey triggerKey in QuartzManage.scheduler.GetTriggerKeys(GroupMatcher<TriggerKey>.GroupEquals(name[i])))

  19. {

  20. //获取触发器的状态

  21. this.jobDataGridView.Rows[i].Cells[2].Value = QuartzManage.shceduler.GetTriggerState(triggerKey);

  22. this.jobDataGridView.Rows[i].Cells[3].Value = triggerKey.Name;

  23. this.jobDataGridView.Rows[i].Cells[4].Value = trigger.Group;

  24. //获取下一次触发时间

  25. ICronTrigger cronTrigger = (ICronTrigger)QuartzManage.scheduler.GetTrigger(triggerKey);

  26. CronExpression cronExpression = new CronExpression(cronTrigger.CronExpressionString);

  27. this.jobDataGridView.Rows[i].Cells[5].Value = cronExpression.GetNextValidTimeAfter(DateTime.Now).Value.ToLocalTime();

  28. }

  29. }

  30. }

        接着就是选中jobDataGridView中的一个任务,这里为了让点击一个单元格就选中整行,添加了一些代码,这里不做细讲,可以去查看我的博客中的第19个例子:https://blog.****.net/qq_41061437/article/details/99940094

        给jobDataGridView添加CellClick事件:

 
  1. private void JobDataGridView_CellClick(object sender, DataGridViewCellEventArgs e)

  2. {

  3. QuartzManage.jobName = (string)jobDataGridView.SelectedRows[0].Cells["Jobs"].Value;

  4. QuartzManage.jobGroupName = (string)jobDataGridView.SelectedRows[0].Cells["JobGroups"].Value;

  5. QuartzManage.triggerName = (string)jobDataGridView.SelectedRows[0].Cells["Triggers"].Value;

  6. QuartzManage.triggerGroupName = (string)jobDataGridView.SelectedRows[0].Cells["TriggerGroupName"].Value;

  7. }

        接着就是选中之后点击暂停按钮,让任务暂停了:

 
  1. private void StopButton_Click(object sender, EventArgs e)

  2. {

  3. TriggerKey triggerKey = new TriggerKey(QuartzManage.triggerName, QuartzManage.triggerGroupName);

  4. if(QuartzManage.jobName.Equals(""))

  5. {

  6. MessageBox.Show("请选择要暂停的任务!");

  7. }

  8. //判断任务状态

  9. else if(QuartzManage.scheduler.GetTriggerState(triggerKey).ToString() == "Paused")

  10. {

  11. MessageBox.Show("该任务已暂停");

  12. }

  13. else

  14. {

  15. QuartzManage.scheduler.PauseTrigger(triggerKey);

  16. JobList();

  17. MessageBox.Show("任务" + QuartzManage.jobName + "已暂停!");

  18. }

  19. }

        接着就是恢复任务:

 
  1. private void ResumeButton_Click(object sender, EventArgs e)

  2. {

  3. TriggerKey triggerKey = new TriggerKey(QuartzManage.triggerName, QuartzManage.triggerGroupName);

  4. if(QuartzManage.jobName.Equals(""))

  5. {

  6. MessageBox.Show("请先选择要恢复的任务!");

  7. }

  8. else if(QuartzManage.scheduler.GetTriggerState(triggerKey).ToString() == "Normal")

  9. {

  10. MessageBox.Show("任务正在运行,无需恢复");

  11. }

  12. else

  13. {

  14. QuartzManage.scheduler.ResumeTrigger(triggerKey);

  15. JobList();

  16. MessageBox.Show("任务" + QuartzManage.jobName + "已恢复!");

  17. }

  18. }

        整个MainView.cs类整合就是:

 
  1. public partial class MainView : Form

  2. {

  3. private static MainView mainView = new MainView();

  4.  
  5. public static MainView GetInstance()

  6. {

  7. return mainView;

  8. }

  9.  
  10. public MainView()

  11. {

  12. InitializeComponent();

  13. }

  14.  
  15. /// <summary>

  16. /// 窗体加载事件

  17. /// <summary>

  18. private void MainView_Load(object sender, EventArgs e)

  19. {

  20. QuartzManage.scheduler.Start();

  21. JobList();

  22. }

  23.  
  24. /// <summary>

  25. /// table展示

  26. /// <summary>

  27. public void JobList()

  28. {

  29. //此方法获取所有的Job的对象

  30. IList<IJobExecutionContext> jobs = QuartzManage.scheduler.GetCurrentlyExecutingJobs();

  31. //此方法获取所有的Job的group的名字

  32. IList<string> name = QuartzManage.scheduler.GetJobGroupNames();

  33. for (int i = 0; i < name.Count; i++)

  34. {

  35. //给jobDataGridView的行赋值,表明多少行

  36. this.jobDataGridView.RowCount = name.Count;

  37. //GetJobKeys获取到所有Job的JobKey,相当于Job信息

  38. foreach (JobKey jobKey in QuartzManage.scheduler.GetJobKeys(GroupMatcher<JobKey>.GroupEquals(name[i])))

  39. {

  40. this.jobDataGridView.Rows[i].Cells[0].Value = jobKey.Name;

  41. this.jobDataGridView.Rows[i].Cells[1].Value = jobKey.Group;

  42. }

  43. //GetTriggerKeys获取到所有Trigger的TriggerKey,相当于Trigger信息

  44. foreach (TriggerKey triggerKey in QuartzManage.scheduler.GetTriggerKeys(GroupMatcher<TriggerKey>.GroupEquals(name[i])))

  45. {

  46. //获取触发器的状态

  47. this.jobDataGridView.Rows[i].Cells[2].Value = QuartzManage.scheduler.GetTriggerState(triggerKey);

  48. this.jobDataGridView.Rows[i].Cells[3].Value = triggerKey.Name;

  49. this.jobDataGridView.Rows[i].Cells[4].Value = triggerKey.Group;

  50. //获取下一次触发时间

  51. ICronTrigger cronTrigger = (ICronTrigger)QuartzManage.scheduler.GetTrigger(triggerKey);

  52. CronExpression cronExpression = new CronExpression(cronTrigger.CronExpressionString);

  53. this.jobDataGridView.Rows[i].Cells[5].Value = cronExpression.GetNextValidTimeAfter(DateTime.Now).Value.ToLocalTime();

  54. }

  55. }

  56. }

  57.  
  58. /// <summary>

  59. /// 单击CellClick事件

  60. /// <summary>

  61. private void JobDataGridView_CellClick(object sender, DataGridViewCellEventArgs e)

  62. {

  63. QuartzManage.jobName = (string)jobDataGridView.SelectedRows[0].Cells["Jobs"].Value;

  64. QuartzManage.jobGroupName = (string)jobDataGridView.SelectedRows[0].Cells["JobGroups"].Value;

  65. QuartzManage.triggerName = (string)jobDataGridView.SelectedRows[0].Cells["Triggers"].Value;

  66. QuartzManage.triggerGroupName = (string)jobDataGridView.SelectedRows[0].Cells["TriggerGroupName"].Value;

  67. }

  68.  
  69. /// <summary>

  70. /// 暂停任务

  71. /// <summary>

  72. private void StopButton_Click(object sender, EventArgs e)

  73. {

  74. TriggerKey triggerKey = new TriggerKey(QuartzManage.triggerName, QuartzManage.triggerGroupName);

  75. if (QuartzManage.jobName.Equals(""))

  76. {

  77. MessageBox.Show("请选择要暂停的任务!");

  78. }

  79. //判断任务状态

  80. else if (QuartzManage.scheduler.GetTriggerState(triggerKey).ToString() == "Paused")

  81. {

  82. MessageBox.Show("该任务已暂停");

  83. }

  84. else

  85. {

  86. QuartzManage.scheduler.PauseTrigger(triggerKey);

  87. JobList();

  88. MessageBox.Show("任务" + QuartzManage.jobName + "已暂停!");

  89. }

  90. }

  91.  
  92. /// <summary>

  93. /// 恢复任务

  94. /// <summary>

  95. private void ResumeButton_Click(object sender, EventArgs e)

  96. {

  97. TriggerKey triggerKey = new TriggerKey(QuartzManage.triggerName, QuartzManage.triggerGroupName);

  98. if (QuartzManage.jobName.Equals(""))

  99. {

  100. MessageBox.Show("请先选择要恢复的任务!");

  101. }

  102. else if (QuartzManage.scheduler.GetTriggerState(triggerKey).ToString() == "Normal")

  103. {

  104. MessageBox.Show("任务正在运行,无需恢复");

  105. }

  106. else

  107. {

  108. QuartzManage.scheduler.ResumeTrigger(triggerKey);

  109. JobList();

  110. MessageBox.Show("任务" + QuartzManage.jobName + "已恢复!");

  111. }

  112. }

  113.  
  114. private void MainView_FormClosing(object sender, FormClosingEventArgs e)

  115. {

  116. DialogResult result = MessageBox.Show("是否关闭窗口?", "关闭", MessageBoxButtons.YesNo, MessageBoxIcon.None);

  117. if (result == DialogResult.No)

  118. {

  119. e.Cancel = true;

  120. }

  121. else

  122. {

  123. e.Cancel = false;

  124. QuartzManage.scheduler.Shutdown();

  125. }

  126. }

  127. }

        启动项目,效果如图:

Quartz.net的使用——显示任务信息、暂停、恢复、显示下次触发时间、禁止并发运行

        下面我们来解释一下其中有些地方,比如Job类中,我使用了MainView mainView = MainView.GetInstance();mainView.JobList();这个的作用是为了更新显示的列表,主要是更新下次触发时间,当Job触发之后,那么我们应该修改其的下次触发时间,这里正常来说用个观察者模式来进行更适合一点,但我这里这样用比较简单,也比较方便,因此这样使用。这样应该就能够理解为什么要使用单例模式了,因为我要在其它类里面调用MainView里面的方法,而我只能给一个窗口显示,因此使用的单例模式。

        这里触发器都是每10秒触发一次,然后在Job1中,休眠了15秒。也就是说Job1至少需要运行15秒,为了能达到上一个任务运行完再执行下一个任务的效果,这里给Job1加入了[DisallowConcurrentExecutionAttribute]的属性。

        当一个任务因为暂停错过触发时间,当再次恢复任务的时候,我们这里直接忽略它,等待下一次触发时间,为了达到此效果,在配置文件中对misfire属性进行了配置:<misfire-instruction>DoNothing</misfire-instruction>,关于其的配置,我们在其的xsd文件中能够清晰的看到几种不同的触发其含有的不同触发机制。

        当然,在Quartz中还有很多不错的方法,具体的可以直接点击进源码中进行查看,之后又机会,我会对里面的方法及其使用再进行深入或者简单的介绍。

        我已经把这个小demo上传到****,资源下载链接:https://download.****.net/download/qq_41061437/11785180