Quartz.NET的使用(附源码)(作者 陈珙)

 

简介

  虽然Quartz.NET被园子里的大神们写烂了,自己还是整理了一篇,结尾会附上源码地址。

  Quartz.NET是一款功能齐全的开源作业调度框架,小至的应用程序,大到企业系统都可以适用。Quartz是作者James House用JAVA语言编写的,而Quartz.NET是从Quartz移植过来的C#版本。

  在一般企业,可以利用Quartz.Net框架做各种的定时任务,例如,数据迁移、跑报表等等。

  另外还有一款Hangfire https://www.hangfire.io/,也是作业调度框架,有自带监控web后台,比Quartz.Net更加易用,简单。但是Cron最低只支持到分钟级。然而Hangfire不是今天的主角,有机会再介绍。

简单例子

新建一个控制台项目,通过Nuget管理下载Quartz包

  1. using System;
  2. using System.Collections.Specialized;
  3. using Quartz;
  4. using Quartz.Impl;
  5.  
  6. namespace QuartzDotNetDemo
  7. {
  8. class Program
  9. {
  10. static void Main(string[] args)
  11. {
  12. //创建一个调度器工厂
  13. var props = new NameValueCollection
  14. {
  15. { "quartz.scheduler.instanceName", "QuartzDotNetDemo" }
  16. };
  17. var factory = new StdSchedulerFactory(props);
  18.  
  19. //获取调度器
  20. var sched = factory.GetScheduler();
  21. sched.Start();
  22.  
  23. //定义一个任务,关联"HelloJob"
  24. var job = JobBuilder.Create<HelloJob>()
  25. .WithIdentity("myJob", "group1")
  26. .Build();
  27.  
  28. //由触发器每40秒触发执行一次任务
  29. var trigger = TriggerBuilder.Create()
  30. .WithIdentity("myTrigger", "group1")
  31. .StartNow()
  32. .WithSimpleSchedule(x => x
  33. .WithIntervalInSeconds()
  34. .RepeatForever())
  35. .Build();
  36.  
  37. sched.ScheduleJob(job, trigger);
  38. }
  39. }
  40.  
  41. public class HelloJob : IJob
  42. {
  43. public void Execute(IJobExecutionContext context)
  44. {
  45. Console.WriteLine("你好");
  46. }
  47. }
  48. }

一个简单的调度任务流程如下:

概念

  有几个重要类和概念需要了解一下:

  • IScheduler - 与调度器交互的主要API.
  • IJob -由执行任务实现的接口。
  • IJobDetail - 定义Job实例
  • ITrigger - 按照定义的时间让任务执行的组件.
  • JobBuilder - 用于定义或者创建JobDetai
  • TriggerBuilder -用于定义或生成触发器实例

  他们之间的关系大概如下:

  当有空闲线程同时,到了该执行的时间,那么就会由Trigger去触发绑定的Job执行它的Excute方法,假如这次没执行完,却到了下一次的运行时间,如果有空闲线程就仍然会再次执行。但是如果没有空闲线程,会等到腾出空闲的线程才会执行,但是超过quartz.jobStore.misfireThreshold设置的时间就会放弃这次的运行。

  当然也可以在Job贴上DisallowConcurrentExecution标签让Job进行单线程跑,避免没跑完时的重复执行。

改造

  在第一个简单的demo里是无法良好的在实际中使用,因此我们需要改造一下。

需要的第三方包:

  • Autofac version="4.6.2"
  • Autofac.Extras.Quartz version="3.4.0"
  • Common.Logging version="3.4.1"
  • Common.Logging.Core version="3.4.1"
  • Common.Logging.Log4Net1213 version="3.4.1"
  • log4net version="2.0.3"
  • Newtonsoft.Json version="10.0.3"
  • Quartz version="2.6.1"
  • Topshelf version="4.0.3"
  • Topshelf.Autofac version="3.1.1"
  • Topshelf.Log4Net version="3.2.0"
  • Topshelf.Quartz version="0.4.0.1"

Topshelf

  Topshelf是一款为了方便安装部署在Windows系统下而诞生的宿主框架,它基于控制台项目,为开发人员带来更方便的调试和部署。

  官网:https://topshelf.readthedocs.io/en/latest/index.html

  那我们可以在Program.cs里写入以下代码:

  1. using Topshelf;
  2. using Topshelf.Autofac;
  3.  
  4. namespace QuartzDotNetDemo
  5. {
  6. class Program
  7. {
  8. static void Main(string[] args)
  9. {
  10. HostFactory.Run(config =>
  11. {
  12. config.SetServiceName(JobService.ServiceName);
  13. config.SetDescription("Quartz.NET的demo");
  14. config.UseLog4Net();
  15. config.UseAutofacContainer(JobService.Container);
  16.  
  17. config.Service<JobService>(setting =>
  18. {
  19. JobService.InitSchedule(setting);
  20. setting.ConstructUsingAutofacContainer();
  21. setting.WhenStarted(o => o.Start());
  22. setting.WhenStopped(o => o.Stop());
  23. });
  24. });
  25. }
  26. }
  27. }

JobService

  此类用来读取配置信息、初始化调度任务和注入ioc容器

  1. public class JobService
  2. {
  3. #region 初始化
  4. private static readonly ILog Log = LogManager.GetLogger(typeof(JobService));
  5.  
  6. private const string JobFile = "JobsConfig.xml";
  7. private static readonly string JobNamespceFormat;
  8. public static readonly string ServiceName;
  9. private static readonly Jobdetail[] JobList;
  10. public static IContainer Container;
  11.  
  12. static JobService()
  13. {
  14. var job = JobFile.XmlToObject<JobsConfig>();
  15.  
  16. ServiceName = job.Quartz.ServiceName;
  17. JobNamespceFormat = job.Quartz.Namespace;
  18. JobList = job.Quartz.JobList.JobDetail;
  19.  
  20. Log.Info("Jobs.xml 初始化完毕");
  21.  
  22. InitContainer();
  23. }
  24. #endregion
  25.  
  26. /// <summary>
  27. /// 初始化调度任务
  28. /// </summary>
  29. /// <param name="svc"></param>
  30. public static void InitSchedule(ServiceConfigurator<JobService> svc)
  31. {
  32. svc.UsingQuartzJobFactory(Container.Resolve<IJobFactory>);
  33.  
  34. foreach (var job in JobList)
  35. {
  36. svc.ScheduleQuartzJob(q =>
  37. {
  38. q.WithJob(JobBuilder.Create(Type.GetType(string.Format(JobNamespceFormat, job.JobName)))
  39. .WithIdentity(job.JobName, ServiceName)
  40. .Build);
  41.  
  42. q.AddTrigger(() => TriggerBuilder.Create()
  43. .WithCronSchedule(job.Cron)
  44. .Build());
  45.  
  46. Log.InfoFormat("任务 {0} 已完成调度设置", string.Format(JobNamespceFormat, job.JobName));
  47. });
  48. }
  49.  
  50. Log.Info("调度任务 初始化完毕");
  51. }
  52.  
  53. /// <summary>
  54. /// 初始化容器
  55. /// </summary>
  56. private static void InitContainer()
  57. {
  58. var builder = new ContainerBuilder();
  59. builder.RegisterModule(new QuartzAutofacFactoryModule());
  60. builder.RegisterModule(new QuartzAutofacJobsModule(typeof(JobService).Assembly));
  61. builder.RegisterType<JobService>().AsSelf();
  62.  
  63. var execDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
  64. var files = Directory.GetFiles(execDir, "QuartzDotNetDemo.*.dll", SearchOption.TopDirectoryOnly);
  65. if (files.Length > )
  66. {
  67. var assemblies = new Assembly[files.Length];
  68. for (var i = ; i < files.Length; i++)
  69. assemblies[i] = Assembly.LoadFile(files[i]);
  70.  
  71. builder.RegisterAssemblyTypes(assemblies)
  72. .Where(t => t.GetInterfaces().ToList().Contains(typeof(IService)))
  73. .AsSelf()
  74. .InstancePerLifetimeScope();
  75. }
  76.  
  77. Container = builder.Build();
  78. Log.Info("IOC容器 初始化完毕");
  79. }
  80.  
  81. public bool Start()
  82. {
  83. Log.Info("服务已启动");
  84. return true;
  85. }
  86.  
  87. public bool Stop()
  88. {
  89. Container.Dispose();
  90. Log.Info("服务已关闭");
  91. return false;
  92. }
  93. }

触发器类型

  一共有4种:

  • WithCalendarIntervalSchedule
  • WithCronSchedule
  • WithDailyTimeIntervalSchedule
  • WithSimpleSchedule

  在项目中使用的是WithCronSchedule,因为cron表达式更加灵活、方便。

Cron表达式

字段名 是否必填 值范围 特殊字符
Seconds YES 0-59 , - * /
Minutes YES 0-59 , - * /
Hours YES 0-23 , - * /
Day of month YES 1-31 , - * ? / L W
Month YES 1-12 or JAN-DEC , - * /
Day of week YES 1-7 or SUN-SAT , - * ? / L #
Year NO empty, 1970-2099 , - * /

例子:

  "0 0/5 * * * ?"    ---- 每5分钟触发一次

  "10 0/5 * * * ?"   -----每5分钟触发一次,每分钟10秒(例如:10:00:10 am,10:05:10,等等)

  "0 0/30 8-9 5,20 * ?" ----在每个月的第5到20个小时之间,每隔半小时就会触发一个触发点。请注意,触发器不会在上午10点触发,仅在8点,8点30分,9点和9点30分

BaseJob

  我们定义一个BaseJob写入公共处理逻辑,例如:业务逻辑禁用、公共异常日志消息推送等等。再由具体的Job去继承重写基类的ExecuteJob,简单的适配器模式运用。

  1. public abstract class BaseJob : IJob
  2. {
  3. protected readonly CommonService CommonService;
  4.  
  5. protected BaseJob(CommonService commonService)
  6. {
  7. CommonService = commonService;
  8. }
  9.  
  10. public void Execute(IJobExecutionContext context)
  11. {
  12. //公共逻辑
  13. CommonService.Enabled();
  14.  
  15. //job逻辑
  16. ExecuteJob(context);
  17. }
  18.  
  19. public abstract void ExecuteJob(IJobExecutionContext context);
  20. }

结束

  最后按照惯例双手奉上demo源码。https://github.com/SkyChenSky/QuartzDotNetDemo.git

  如果错误麻烦在下面评论指出,我会及时修改。

作  者: 陈珙
出  处:http://www.cnblogs.com/skychen1218/ 
关于作者:专注于微软平台的项目开发。如有问题或建议,请多多赐教! 
版权声明:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。 
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是作者坚持原创和持续写作的最大动力

 

qurtz.net的更多相关文章

  1. qurtz.net(转载)

    Quartz+TopShelf实现Windows服务作业调度   Quartz:首先我贴出来了两段代码(下方),可以看出,首先会根据配置文件(quartz.config),包装出一个Quartz.Co ...

  2. 使用Timer和ScheduledThreadPoolExecutor执行定时任务

    Java使用Timer和ScheduledThreadPoolExecutor执行定时任务 定时任务是在指定时间执行程序,或周期性执行计划任务.Java中实现定时任务的方法有很多,主要JDK自带的一些 ...

  3. java并发面试

    1.在java中守护线程和本地线程区别? java中的线程分为两种:守护线程(Daemon)和用户线程(User). 任何线程都可以设置为守护线程和用户线程,通过方法Thread.setDaemon( ...

  4. Java并发编程75道面试题及答案

    1.在java中守护线程和本地线程区别? java中的线程分为两种:守护线程(Daemon)和用户线程(User). 任何线程都可以设置为守护线程和用户线程,通过方法Thread.setDaemon( ...

  5. Java并发编程75个问答

    1.在java中守护线程和本地线程区别? java中的线程分为两种:守护线程(Daemon)和用户线程(User). 任何线程都可以设置为守护线程和用户线程,通过方法Thread.setDaemon( ...

  6. Java并发编程73道面试题及答案

    原文出处:https://blog.csdn.net/qq_34039315/article/details/7854931 1.在java中守护线程和本地线程区别? java中的线程分为两种:守护线 ...

  7. Java并发编程73道面试题及答案 —— 面试稳了

    今天主要整理一下 Java 并发编程在面试中的常见问题,希望对需要的读者有用. 1.在java中守护线程和本地线程区别? java中的线程分为两种:守护线程(Daemon)和用户线程(User). 任 ...

  8. Java——并发编程

    1.在java中守护线程和本地线程区别? java中的线程分为两种:守护线程(Daemon)和用户线程(User). 任何线程都可以设置为守护线程和用户线程,通过方法Thread.setDaemon( ...

  9. Java并发编程总结

    基础概念 1.什么是原子操作?在Java Concurrency API中有哪些原子类(atomic classes)?原子操作(atomic operation)意为"不可被中断的一个或一 ...

随机推荐

  1. T-SQL 有参数存储过程的创建与执行

    use StudentManager go if exists(select * from sysobjects where name='usp_ScoreQuery2') drop procedur ...

  2. Android Gradle插件(plugin)版本(version)与Gradle、SDK Build Tools版本关系

    具体关系如下图: 比如,Android Studio 2.0发布,其中有个新功能“Instant Run”,需要Android Gradle Plugin版本2.0.0以上,那么我们项目的.gradl ...

  3. MySQL8.0.12版本密码修改策略问题

    查看密码策略(修改临时密码之后才可查看) show variables like 'validate_password%'; 8之前 validate_password_     8之后validat ...

  4. MySQL建立索引,触发器

    创建索引: ALTER TABLE <表名> ADD INDEX (<字段>); >ALTER TABLE `table_name` ADD PRIMARY KEY (` ...

  5. 更优雅的使用Git

    JavaScript之禅已经发过两篇 Git 相关的文章了.一篇文章,教你学会Git :浅显易懂,如果你还不会 Git 可以先去看看.Git的奇技淫巧 :介绍了一些实用的操作.今天为大家带来第三篇,如 ...

  6. JQuery字符串的操作

    一.String对象属性 1.length属性: length算是字符串中非常常用的一个属性了,它的功能是获取字符串的长度.当然需要注意的是js中的中文每个汉字也只代表一个字符,这里可能跟其他语言有些 ...

  7. liinux安装 mysql 及主从复制

    mariadb其实就是mysqlmysql已经被oracle收购,它即将闭源,马上要开始收费了因此还想免费试用开源的数据库mysql,就在centos7上,将mysql分支为mariadb 安装mar ...

  8. python连接数据库——create_engine和conn.cursor

    python操作数据库的方法: 一种是导入sqlalchemy包,另一种是导入psycopg2包. 具体用法如下(此处以postgre数据库举例) 第一种: # 导入包 from sqlalchemy ...

  9. python-多线程和线程池

    import threading # 点击查看它的用法 传统多线程方案会使用“即时创建, 即时销毁”的策略. from multiprocessing.dummy import Pool # 线程池 ...

  10. bootstrap-datepicker实现日期input readonly 标签中选择时间功能

    引用datepicker css,js,zh-CH文件 ps: 都是基于bootstrap,所以得先引入bootstrap文件才可以使用 <link href="https://cdn ...