zoukankan      html  css  js  c++  java
  • 对简单的Quartz了解的不简单一些

    Quartz.Net的关键接口

    • Scheduler - 与调度程序交互的主要API。[ IScheduler]
    • Job - 由希望由调度程序执行的组件实现的接口。[IJob]
    • JobDetail - 用于定义作业的实例。[IJobDetail]
    • Trigger(即触发器) - 定义执行给定作业的计划的组件。[ITrigger]
    • JobBuilder - 用于定义/构建JobDetail实例,用于定义作业的实例。
    • TriggerBuilder - 用于定义/构建触发器实例。
        public class HelloJob : IJob
        {
            public Task Execute(IJobExecutionContext context)
            {
                Console.WriteLine("Hello World");
                return Task.CompletedTask;
            }
        }
        
        class Program
        {
            static async Task Main(string[] args)
            {
                StdSchedulerFactory factory = new StdSchedulerFactory();
                var scheduler = await factory.GetScheduler();
                await scheduler.Start();
                // define the job and tie it to our HelloJob class
                IJobDetail job = JobBuilder.Create<HelloJob>()
                    .WithIdentity("myJob", "group1")
                    .Build();
    
                // Trigger the job to run now, and then every 3 seconds
                ITrigger trigger = TriggerBuilder.Create()
                    .WithIdentity("myTrigger", "group1")
                    .StartNow()
                    .WithSimpleSchedule(x => x
                        .WithIntervalInSeconds(3)
                        .RepeatForever())
                .Build();
                await scheduler.ScheduleJob(job, trigger);
    
                // 30秒后停止调度计划
                await Task.Delay(1000 * 30);
                await scheduler.Shutdown();
            }
        }
    

    Scheduler

    Scheduler的生命期,从SchedulerFactory创建它时开始,到Scheduler调用Shutdown()方法时结束。

    Scheduler被创建后,可以增加、删除和列举Job和Trigger,以及执行其它与调度相关的操作(如暂停Trigger)。

    Scheduler只有在调用Start()方法后,才会真正地触发trigger 。

    Job与JobDetail

    下面讲到的 Job 都是指的是实现 IJob 的类,例如:HelloJob

    • JobDetail

      1. JobDetail实例是通过JobBuilder类创建的。
          // define the job and tie it to our HelloJob class
          IJobDetail job = JobBuilder.Create<HelloJob>()
              .WithIdentity("myJob", "group1")
              .Build();
      
      1. 注册到 Scheduler 的不是Job对象,而是 JobDetail 实例 。Job对象只是 JobDetail 实例的一部分。
          await scheduler.ScheduleJob(job, trigger);
      

      可以只创建一个job类,然后创建多个与该Job关联的JobDetail实例,每一个实例都有自己的属性集和JobDataMap,最后,将所有的实例都加到scheduler中 。

      1. 通过JobDetail对象,可以给job实例配置的其它属性有:

        Durability:[StoreDurably()]如果一个job是非持久的,当没有活跃的trigger与之关联的时候,会被自动地从scheduler中删除。也就是说,非持久的job的生命期是由trigger的存在与否决定的;

        RequestsRecovery:[RequestRecovery()]如果一个job是可恢复的,并且在其执行的时候,scheduler发生硬关闭(hard shutdown)(比如运行的进程崩溃了,或者关机了),则当scheduler重新启动的时候,该job会被重新执行。此时,该job的JobExecutionContext.isRecovering() 返回true。

    • Job

      1. 每一个Job都必须实现IJob。例如上面的 HelloJob。这个类仅仅表明该job需要完成什么类型的任务,除此之外,Quartz还需要知道该Job实例所包含的属性,这将由JobDetail类来完成。

            public class HelloJob : IJob
            {
                public Task Execute(IJobExecutionContext context)
                {
                    Console.WriteLine("Hello World");
                    return Task.CompletedTask;
                }
            }
        
      2. Job的生命周期

          // define the job and tie it to our HelloJob class
          IJobDetail job = JobBuilder.Create<HelloJob>()
              .WithIdentity("myJob", "group1")
              .Build();
      
          // Trigger the job to run now, and then every 40 seconds
          ITrigger trigger = TriggerBuilder.Create()
              .WithIdentity("myTrigger", "group1")
              .StartNow()
              .WithSimpleSchedule(x => x
                  .WithIntervalInSeconds(3)
                  .RepeatForever())
          	.Build();
          await scheduler.ScheduleJob(job, trigger);
      

      可以看到,我们传给scheduler一个JobDetail实例,因为我们在创建JobDetail时,将要执行的job的类名(HelloJob)传给了JobDetail,所以scheduler就知道了要执行何种类型的job;

      每次当scheduler执行job时,在调用其Execute(…)方法之前会创建该类的一个新的实例;执行完毕,对该实例的引用就被丢弃了,实例会被垃圾回收。

      Job 的实例要到该执行它们的时候才会实例化出来。每次 Job 被执行,一个新的 Job 实例会被创建。也就是说 Job 不必担心线程安全性,因为同一时刻仅有一个线程去执行给定 Job 类的实例,甚至是并发执行同一 Job 也是如此。

      这种执行策略需要我们注意:
      
      * job必须有一个无参的构造函数;
      * 在job类中,不应该定义有状态的数据属性,因为在job的多次执行中,这些属性的值不会保留。 
      

      那么如何给job实例增加属性或配置呢?如何在job的多次执行中,跟踪job的状态呢?答案就是:JobDataMap,JobDetail对象的一部分。 (后面会详细介绍用法)

    Trigger

    Trigger对象是用来触发执行Job的。当调度一个job时,我们实例一个触发器然后调整它的属性来满足job执行的条件。触发器也有一个和它相关的JobDataMap,它是用来给被触发器触发的job传参数的。

    1. SimpleTrigger可以满足的调度需求是:在具体的时间点执行一次,或者在具体的时间点执行,并且以指定的间隔重复执行若干次。

      	ITrigger trigger = TriggerBuilder.Create()
      		.WithIdentity("myTrigger", "group1")
      		.StartNow()
      		.WithPriority(5)
      		.WithSimpleSchedule(x => x
      			.WithIntervalInSeconds(3)
      			.RepeatForever())
      		.Build();
      
    2. CronTrigger基于日历的概念进行作业启动计划。

      	ITrigger trigger1 = TriggerBuilder.Create()
      		.WithIdentity("myTrigger1", "group1")
      		.ForJob(job)
      		.WithCronSchedule("0 0/3 * * ?")
              .Build();
      

      CronExpression表达式 :https://www.cnblogs.com/yaowen/p/3779284.html

    3. 优先级(priority) 方法:.WithPriority(5)

    如果你的trigger很多(或者Quartz线程池的工作线程太少),Quartz可能没有足够的资源同时触发所有的trigger;这种情况下,你可能希望控制哪些trigger优先使用Quartz的工作线程,要达到该目的,可以在trigger上设置priority属性。

    比如,你有N个trigger需要同时触发,但只有Z个工作线程,优先级最高的Z个trigger会被首先触发。如果没有为trigger设置优先级,trigger使用默认优先级,值为5;priority属性的值可以是任意整数,正数、负数都可以。(只有同时触发的trigger之间才会比较优先级。10:59触发的trigger总是在11:00触发的trigger之前执行。)

    Job与Trigger

    Trigger对于job而言就好比一个驱动器;没有触发器来定时驱动作业,作业就无法运行;

    对于Job而言,一个job可以对应多个Trigger,但对于Trigger而言,一个Trigger只能对应一个job;所以一个Trigger 只能被指派给一个 Job;如果你需要一个更复杂的触发计划,你可以创建多个 Trigger 并指派它们给同一个 Job。(Trigger实例对应一个JobDetail实例,Job类可以添加到多个JobDetail实例中)

    Scheduler 是基于配置在 Job上的 Trigger 来决定正确的执行计划的。

    JobDataMap

    重点介绍一下JobDataMap,这是一个非常好用且被我们忽视的属性。

    JobDataMap中可以包含不限量的(序列化的)数据对象,在job实例执行的时候,可以使用其中的数据;JobDataMap是 IDictionary<TKey, TValue> 接口的一个实现,额外增加了一些便于存取基本类型的数据的方法。

    JobDetail 和 Trigger 示例都可以设置 JobDataMap(通过UsingJobData()方法)

    	IJobDetail job = JobBuilder.Create<HelloJob>()
    		.WithIdentity("myJob", "group1")
    		.UsingJobData("jobDetail", "J")
    		.Build();
    	
    	ITrigger trigger = TriggerBuilder.Create()
    		.WithIdentity("myTrigger", "group")
    		.UsingJobData("trigger", "T")
    		.WithCronSchedule("0/3 * * * * ?")
    		.Build();
    	await scheduler.ScheduleJob(job, trigger);
    

    传递的值可以通过IJob.Execute(IJobExecutionContext context) 中context.MergedJobDataMap获取

        public class HelloJob : IJob
        {
            public Task Execute(IJobExecutionContext context)
            {
                var jobDataMap = context.MergedJobDataMap;
                Console.WriteLine($"{DateTime.Now.ToLongTimeString()}-jobDetail:{jobDataMap["jobDetail"]}-trigger:{jobDataMap["trigger"]}");
                return Task.CompletedTask;
            }
        }
    

    执行结果:

    11:36:36-jobDetail:J-trigger:T
    11:36:39-jobDetail:J-trigger:T
    11:36:42-jobDetail:J-trigger:T
    ...
    

    需要注意的是:

    1. MergedJobDataMap是将JobDetail.JobDataMap和Trigger.JobDataMap的值合并的,如果key重复,将读取Trigger中相同可以的值。
    2. 可以通过 context.JobDetail.JobDataMap 和 context.Trigger.JobDataMap分别读取。
    3. 在IJob.Execute()方法中修改任何JobDataMap值,是不会影响到下次Job执行JobDataMap的值的。只在本次Job中有效。

    那有什么办法让本次执行的状态修改,影响到以一次执行呢?即修改JobDataMap的值,下一次执行取出的是上一次修改过的?办法是有的,给Job类打标签

    Job属性标签

    PersistJobDataAfterExecution:将该注解加在job类上,告诉Quartz在成功执行了job类的execute方法后(没有发生任何异常),更新JobDetail中JobDataMap的数据,使得该job(即JobDetail)在下一次执行的时候,JobDataMap中是更新后的数据,而不是更新前的旧数据。

        [PersistJobDataAfterExecution]
        public class HelloJob : IJob
        {
            public Task Execute(IJobExecutionContext context)
            {
                var count = context.JobDetail.JobDataMap.GetInt("Count");
                Console.WriteLine($"{DateTime.Now.ToLongTimeString()}-Count:{count}");
    
                count++;
                context.JobDetail.JobDataMap.Put("Count", count);
    
                return Task.CompletedTask;
            }
        }                                       
    
    	StdSchedulerFactory factory = new StdSchedulerFactory();
    	var scheduler = await factory.GetScheduler();
    	await scheduler.Start();
    	
    	IJobDetail job1 = JobBuilder.Create<HelloJob>()
    		.WithIdentity("myJob.1", "group")
    		.UsingJobData("Count", "1")
    		.Build();
    	
    	ITrigger trigger1 = TriggerBuilder.Create()
    		.WithIdentity("myTrigger.1", "group")
    		.WithCronSchedule("0/3 * * * * ?")
    		.Build();
    					
    	await scheduler.ScheduleJob(job1, trigger1);  
    

    执行结果:

    14:17:09-Count:1
    14:17:12-Count:2
    14:17:15-Count:3
    ...
    

    如果使用了[PersistJobDataAfterExecution]标签,将强烈建议同时使用[DisallowConcurrentExecution]标签,因为当同一个job(JobDetail)的两个实例被并发执行时,由于竞争,JobDataMap中存储的数据很可能是不确定的。

    DisallowConcurrentExecution:将该注解加到job类上, 告诉Quartz不要并发地执行同一个job定义的多个实例 。

    举例说明就是:将 HelloJob 添加到 job1(IJobDetail )

    	IJobDetail job1 = JobBuilder.Create<HelloJob>()
    		.WithIdentity("myJob.1", "group")
    		.UsingJobData("flag", "myJob.1")
    		.Build();
    

    job1绑定触发器trigger1(ITrigger) 每秒执行一次

    	ITrigger trigger1 = TriggerBuilder.Create()
    		.WithIdentity("myTrigger.1", "group")
    		.WithCronSchedule("0/1 * * * * ?")
    		.Build();
    	await scheduler.ScheduleJob(job1, trigger1);
    

    HelloJob 的执行耗时为2秒

        public class HelloJob : IJob
        {
            public Task Execute(IJobExecutionContext context)
            {
                var flag = context.MergedJobDataMap.GetString("flag");
                //模拟耗时2秒
                Thread.Sleep(2000);
                Console.WriteLine($"{DateTime.Now.ToLongTimeString()}-flag:{flag}");
    
                return Task.CompletedTask;
            }
        }
    

    首先不打 DisallowConcurrentExecution 标签,看看输出结果:

    任务启动:14:39:05
    14:39:07-flag:myJob.1
    14:39:08-flag:myJob.1
    14:39:09-flag:myJob.1
    14:39:10-flag:myJob.1
    ...
    

    通过结果输入,可以看到,第一次任务是从14:39:05开始,14:39:07结束;第二次的任务接时间是14:39:08,退出开始时间是14:39:06,以此类推...也就说前一个任务未完成,并不影响之后任务的开始

    接着我们个 HelloJob 打上 DisallowConcurrentExecution 属性标签

        [DisallowConcurrentExecution]
        public class HelloJob : IJob
        {
            public Task Execute(IJobExecutionContext context)
            {
                var flag = context.MergedJobDataMap.GetString("flag");
                //模拟耗时2秒
                Thread.Sleep(2000);
                Console.WriteLine($"{DateTime.Now.ToLongTimeString()}-flag:{flag}");
    
                return Task.CompletedTask;
            }
        }
    

    看看输出结果:

    任务启动:14:47:34
    14:47:36-flag:myJob.1
    14:47:38-flag:myJob.1
    14:47:40-flag:myJob.1
    14:47:42-flag:myJob.1
    ...
    

    通过结果输出可以看到,文本输出是每两秒一次,也就说,前一个任务未完成,之后任务不会开始。即不会创建一个新的 HelloJob 实例。这也就不会并发处理任务了。

    需要注意的是,DisallowConcurrentExecution 属性标签,限制的是 JobDetail ,而不是 Job(HelloJob)。同一个JobDetail 实例创建的 Job 不会并发。但,不同的 JobDetail 实例创建的 Job 是可以并发的。

    我们再创建一组关于 HelloJob 的任务:job2(IJobDetail),trigger2(ITrigger),HelloJob 不变。

        class Program
        {
            static async Task Main(string[] args)
            {
                Console.WriteLine($"任务启动:{DateTime.Now.ToLongTimeString()}");
                StdSchedulerFactory factory = new StdSchedulerFactory();
                var scheduler = await factory.GetScheduler();
                await scheduler.Start();
                IJobDetail job1 = JobBuilder.Create<HelloJob>()
                    .WithIdentity("myJob.1", "group")
                    .UsingJobData("flag", "myJob.1")
                    .Build();
                ITrigger trigger1 = TriggerBuilder.Create()
                    .WithIdentity("myTrigger.1", "group")
                    .WithCronSchedule("0/1 * * * * ?")
                    .Build();
                
                IJobDetail job2 = JobBuilder.Create<HelloJob>()
                    .WithIdentity("myJob.2", "group")
                    .UsingJobData("flag", "myJob.2")
                    .Build();
                ITrigger trigger2 = TriggerBuilder.Create()
                    .WithIdentity("myTrigger.2", "group")
                    .WithCronSchedule("0/1 * * * * ?")
                    .Build();
    
                await scheduler.ScheduleJob(job1, trigger1);
                await scheduler.ScheduleJob(job2, trigger2);
    
                Console.ReadKey();
            }
        }
    

    看看输出结果:

    任务启动:15:02:24
    15:02:26-flag:myJob.1
    15:02:26-flag:myJob.2
    15:02:28-flag:myJob.1
    15:02:28-flag:myJob.2
    15:02:30-flag:myJob.1
    15:02:30-flag:myJob.2
    ...
    

    通过结果输出可以看到,同一个JobDetail,是没有每秒执行的,即前一个任务没有完成,后面的任务不会执行。但不同的JobDetail,却在同一时间执行了。

    Job的配置

    像上面示例中,我们配置Job,基本都是硬编码,我们可以把配置移到配置文件中,方便修改和添加

    默认配置文件名:quartz_jobs.xml

    <?xml version="1.0" encoding="UTF-8"?>
    
    <job-scheduling-data xmlns="http://quartznet.sourceforge.net/JobSchedulingData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0">
      <processing-directives>
        <overwrite-existing-data>true</overwrite-existing-data>
      </processing-directives>
    
      <schedule>
        <job>
          <name>myJob.1</name>
          <group>group</group>
          <description>Hello World!</description>
          <job-type>Sayook.Schedule.Client.HelloJob, Sayook.Schedule.Client</job-type>
          <job-data-map>
            <entry>
              <key>flag</key>
              <value>myJob.1</value>
            </entry>
          </job-data-map>
        </job>
        <trigger>
          <cron>
            <name>myTrigger.1</name>
            <group>group</group>
            <description>Hello World! </description>
            <job-name>myJob.1</job-name>
            <job-group>group</job-group>
            <job-data-map>
              <entry>
                <key>key</key>
                <value>1</value>
              </entry>
            </job-data-map>
            <cron-expression>0/3 * * * * ?</cron-expression>
          </cron>
        </trigger>
      </schedule>
    </job-scheduling-data>
    

    我们可以将 job_scheduling_data_2_0.xsd 文件添加到 VisualStudio2019 的XML架构(直接在IDE顶部搜索框搜索xml架构),我们在编写xml配置文件的时候就会有提示和验证了。

        class Program
        {
            static async Task Main(string[] args)
            {
                var properties = new NameValueCollection
                {
                    ["quartz.plugin.xml.type"] = "Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin, Quartz.Plugins",
                    ["quartz.plugin.xml.fileNames"] = "quartz_jobs.xml",
                    // this is the default
                    ["quartz.plugin.xml.FailOnFileNotFound"] = "true",
                    // this is not the default
                    ["quartz.plugin.xml.failOnSchedulingError"] = "true"
                };
    
                StdSchedulerFactory factory = new StdSchedulerFactory(properties);
                var scheduler = await factory.GetScheduler();
                await scheduler.Start();
                Console.ReadKey();
            }
        }
    

    ["quartz.plugin.xml.FailOnFileNotFound"] = "true",
    ["quartz.plugin.xml.failOnSchedulingError"] = "true"

    上面两个配置文件强烈建议添加,以为这样,配置文件错误了,会有详细的异常信息抛出,以便修改,负责是不会报错,很难定位问题。

    使用配置文件,要引用包:Quartz.Plugins

    相关配置可查看文章:Quartz.NET 配置文件详解 https://www.cnblogs.com/abeam/p/8044460.html

    应用示例

    基于.NetCore的依赖注入,对Quartz.Net的使用

    示例里面包含:

    1. 可视化面板控制的调度应用[Sayook.Schedule.Manager]
    2. 使用控制台应用程序创建的 泛型主机 调用应用[Sayook.Schedule.Client]

    通过xml文件配置Job,后续维护、新增Job,对代码的改动都非常小, 是最轻量级的使用。

    示例代码地址: https://gitee.com/sayook/Sayook.Schedule.Framework

  • 相关阅读:
    Malware Sample Sources for Researchers
    How to Allow MySQL Client to Connect to Remote MySQL server
    指標和函數
    ”十六“进制查看器
    解決svchost.exe 100%的問題 (真的中毒了)
    C#中Class与Struct区别
    js获取asp.net服务器端控件Label,TextBox,RadioButtonList,DropDownList的值
    大象之UML (一) 准备
    大象之UMl(二)UML核心元素
    配置.NET运行环境
  • 原文地址:https://www.cnblogs.com/sayook/p/12957449.html
Copyright © 2011-2022 走看看