AspNet Core结合Quartz使用定时任务且通过注入缓存或者配置参数
一、经常在项目会用到定时任务同步数据或更新缓存等操作,在很久以前我们可能经常会用一个多线程或timer来做定时任务,这样能实现比较简单轻量级的任务;对于任务多且都调用频率不一样的任务,我们都会用到Quartz.Net这个组件;
Quartz.NET是一个强大、开源、轻量的作业调度框架,你能够用它来为执行一个作业而创建简单的或复杂的作业调度。它有很多特征,如:数据库支持,集群,插件,支持cron-like表达式等等
二、 接下来简单演示一下Quartz使用:
2.1 首先新建一个AspNet Core API 项目,通过nuget包管理器安装引用Quartz
2.2 新建一个模拟任务类UserInfoSyncjob 必须继承IJob接口
namespace QuartzDemo.Quarzs { public class UserInfoSyncjob : IJob { public Task Execute(IJobExecutionContext context) { return Task.Run(() => { //..... Console.WriteLine($"{DateTime.Now.ToString()}:开始执行同步第三方数据"); //....同步操作 }); } } }
2.2 声明一个启动类QuartzStartup,来控制任务启动关闭等方法
添加启动方法
public async Task<string> Start() { //1、声明一个调度工厂 _schedulerFactory = new StdSchedulerFactory(); //2、通过调度工厂获得调度器 _scheduler = await _schedulerFactory.GetScheduler(); //3、开启调度器 await _scheduler.Start(); //4、创建一个触发器 var trigger = TriggerBuilder.Create() .WithSimpleSchedule(x => x.WithIntervalInSeconds(2).RepeatForever())//每两秒执行一次 .Build(); //5、创建任务 var jobDetail = JobBuilder.Create<UserInfoSyncjob>() .WithIdentity("job", "group") .Build(); //6、将触发器和任务器绑定到调度器中 await _scheduler.ScheduleJob(jobDetail, trigger); return await Task.FromResult("将触发器和任务器绑定到调度器中完成"); }
2.3 在网站启动完成时调用QuartzStartup的Start方法开启任务
先注入 Quartz调度类
添加网站启动开始方法
2.4、运行效果,运行之前将控制台开启(方便查看任务是否在执行,实际环境可写日志)
该调度任务完成,上方定义的触发器是2秒一次,所以该任务每隔2秒执行;(也可以通过配置文件,控制执行平率,cron表达式可以很好控制)
三、第二结简单演示了Quartz的基本用法,本文重点不是主要讲解Quartz的用法,上方只是为了没使用过Quartz的同行有个简单映像,如果想详细学习,博客园有很多类似的文章,也可以和我探讨一下!
本文重点是每个任务类怎么通过注入获取其他类的使用及参数配置类等等;
假如有这样一个需求,UserInfoSyncjob同步任务里面需要配置数据库连接参数和日志记录、缓存记录等,在之前我们可能通过配置类、日志类、缓存类以工厂形式单例创建获取。
在AspNet Core自带IOC容器框架,很多配置类、日志类、缓存类等等,在全局很多地方都会使用,我们现在做法就是把这些类注入到IOC容器中,如果需要的只需要从构造方法中获取;
我们都知道如果一个从构造方法中获取IOC容器里面的类型实例,必须该类型也要主要到IOC容器中,这样我们就要想办法把UserInfoSyncjob通过容器来创建生产;
通过源码发现在Quartz有一个默认的生成job的工厂类Quartz.Simpl.SimpleJobFactory
using System; using Quartz.Logging; using Quartz.Spi; using Quartz.Util; namespace Quartz.Simpl { /// <summary> /// The default JobFactory used by Quartz - simply calls /// <see cref="ObjectUtils.InstantiateType{T}" /> on the job class. /// </summary> /// <seealso cref="IJobFactory" /> /// <seealso cref="PropertySettingJobFactory" /> /// <author>James House</author> /// <author>Marko Lahma (.NET)</author> public class SimpleJobFactory : IJobFactory { private static readonly ILog log = LogProvider.GetLogger(typeof (SimpleJobFactory)); /// <summary> /// Called by the scheduler at the time of the trigger firing, in order to /// produce a <see cref="IJob" /> instance on which to call Execute. /// </summary> /// <remarks> /// It should be extremely rare for this method to throw an exception - /// basically only the case where there is no way at all to instantiate /// and prepare the Job for execution. When the exception is thrown, the /// Scheduler will move all triggers associated with the Job into the /// <see cref="TriggerState.Error" /> state, which will require human /// intervention (e.g. an application restart after fixing whatever /// configuration problem led to the issue with instantiating the Job). /// </remarks> /// <param name="bundle">The TriggerFiredBundle from which the <see cref="IJobDetail" /> /// and other info relating to the trigger firing can be obtained.</param> /// <param name="scheduler"></param> /// <returns>the newly instantiated Job</returns> /// <throws> SchedulerException if there is a problem instantiating the Job. </throws> public virtual IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler) { IJobDetail jobDetail = bundle.JobDetail; Type jobType = jobDetail.JobType; try { if (log.IsDebugEnabled()) { log.Debug($"Producing instance of Job '{jobDetail.Key}', class={jobType.FullName}"); } return ObjectUtils.InstantiateType<IJob>(jobType); } catch (Exception e) { SchedulerException se = new SchedulerException($"Problem instantiating class '{jobDetail.JobType.FullName}'", e); throw se; } } /// <summary> /// Allows the job factory to destroy/cleanup the job if needed. /// No-op when using SimpleJobFactory. /// </summary> public virtual void ReturnJob(IJob job) { var disposable = job as IDisposable; disposable?.Dispose(); } } }
SimpleJobFactory 实现了IJobFactory接口,通过源码发现我们如果要替换该工厂来控制job的生成,只需要创建一个IOCJobFactory来替换默认job工厂就行
public class IOCJobFactory : IJobFactory { private readonly IServiceProvider _serviceProvider; public IOCJobFactory(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler) { return _serviceProvider.GetService(bundle.JobDetail.JobType) as IJob; } public void ReturnJob(IJob job) { var disposable = job as IDisposable; disposable?.Dispose(); } }
在调度任务类里面重新设置job工厂 _scheduler.JobFactory = _iocJobfactory;
在IOC中注入 UserInfoSyncjob、StdSchedulerFactory、IOCJobFactory
services.AddTransient<UserInfoSyncjob>(); // 这里使用瞬时依赖注入 services.AddSingleton<ISchedulerFactory, StdSchedulerFactory>();//注册ISchedulerFactory的实例。 services.AddSingleton<QuartzStartup>(); services.AddSingleton<IJobFactory,IOCJobFactory>();
修改UserInfoSyncjob任务类,可以通过构造方法注入的方式从容器中拿到日志实现类、缓存类等等
public class UserInfoSyncjob : IJob { private readonly ILogger<UserInfoSyncjob> _logger; // private readonly ICache _cache; public UserInfoSyncjob(ILogger<UserInfoSyncjob> logger) { //_cache = cache; _logger = logger;// EnginContext.Current.Resolve<ILogger<UserInfoSyncjob>>(); } public Task Execute(IJobExecutionContext context) { return Task.Run(() => { //..... // Console.WriteLine($"{DateTime.Now.ToString()}:开始执行同步第三方数据"); _logger.LogInformation ($"{DateTime.Now.ToString()}:开始执行同步第三方数据"); //....同步操作 // 我们都知道如果一个从构造方法中获取IOC容器里面的类型,必须该类型也要主要到IOC容器中; }); } }
调整后运行截图
具体详细步骤请看源码:https://github.com/lxshwyan/QuartzDemo.git