Quartz.NET这么NB的作业调度系统,不会还行?
?
今天介绍一下Quartz.NET的托管运行,官网传送门。
一、前言
Quartz.NET,按官网上的说法,是一款功能齐全的任务调度系统,从小型应用到大型企业级系统都能适用。在众多项目中,Quartz.NET以可靠、集群的方式,被用作在定时器上运行后台任务的一种方式。
Quartz.NET主要完成两个方面的内容:
@H_404_18@基于时间计划的后台作业;
@H_404_18@基于因时间计划的触发的任务运行。
?
ASP.NET Core本身对于通过托管服务运行后台任务就支持的很好。当ASP.NET启动托管服务时,应用程序启动,并在生命周期内在后台运行。通过创建Quartz.NET托管服务,可以使用标准的.Net Core托管服务,在后台运行任务。
Quartz.NET可以创建定时的任务,例如每十分钟运行一个任务。除此之外,Quartz.NET还可以通过Cron触发器,定义任务在特定的日子或特定的时间运行,例如每天凌晨两点执行一个任务。它还允许以集群的方式运行应用程序的多个实例,以便在任何时间确保只有一个实例运行给定的任务。
?
下面,就针对这些特性和功能,进行详细的说明。
????为防止非授权转发,这儿给出本文的原文链接:https://www.cnblogs.com/tiger-wang/p/13861121.html
二、安装Quartz.NET
Quartz.NET提供了NuGet包,所以安装很简单:
%?dotnet?add?package?quartz
这是个司机就知道,不详说了。
看一下安装后的.csproj 文件内容:
<Project?Sdk="Microsoft.NET.Sdk.Web"> ??PropertyGroup> ????TargetFramework>netcoreapp3.1</TargetFramework> ??PropertyGroup> ??ItemGroup> ????PackageReference?Include="quartz"?Version="3.2.2"?/> ??ItemGroup> Project>
三、通过IJob创建任务类
我们用个例子来说明 - 创建一个Demo 的实现。它将写入ILogger<> 。我们会使用Quartz.NET的接口IJob 来实现,并使用依赖注入将日志注入到构造函数中。
[DisallowConcurrentExecution] public?class?DemoJob?:?IJob { ????private?readonly?ILogger<DemoJob>?_logger; ????public?DemoJob(ILogger<DemoJob>?logger) ????{ ????????_logger?=?logger; ????}
????public?Task?Execute(IJobExecutionContext?context) ????{ ????????_logger.LogInformation("Demo?!"); ????????return?Task.CompletedTask; ????} }
在类的前面,我用了一个DisallowConcurrentExecution 属性。这个属性可以防止Quartz.NET同时运行相同的作业。
四、通过IJobFactory创建任务工厂
通常情况下,Quartz.NET会使用Activator.CreateInstance 来创建作业的实例。
在我们这个例子里,我们换一种方式。使用IJobFactory 实现,并与ASP.NET Core的依赖注入容器挂钩。
SingletonJobFactory?:?IJobFactory { ????private?readonly?IServiceProvider?_serviceProvider; ????SingletonJobFactory(IServiceProvider?serviceProvider) ????{ ????????_serviceProvider?=?serviceProvider; ????}
????public?IJob?NewJob(TriggerFiredBundle?bundle,?IScheduler?scheduler) ????{ ????????return?_serviceProvider.GetrequiredService(bundle.JobDetail.JobType)?as?IJob; ????}
????public?void?ReturnJob(IJob?job) ????{ ????} }
这个IJobFactory 的实现,在构造函数中引入IServiceProvider ,并实现接口。
接口中,最重要的是NewJob() 方法。这个方法需要返回Quartz.NET调度器请求的IJob 。在我们的例子中,我们直接委托给IServiceProvider ,并让DI容器找到所需的实例。
ReturnJob() 方法是调度程序返回和销毁IJobFactory 创建的作业的地方。不过,因为我们使用了IServiceProvider ,而它没有提供这样的处理。所以,从安全的角度,应该使用单例作业。
五、配置作业
在第三节中,我们创建了一个IJob 的实现。这个实现直接使用就可以。
但是,我们这儿要加点内容。我们把Quartz 的托管服务做成一个通用实现,来调度任意的作业。因此,我们创建一个简单的DTO,并使用它来定义一个给定作业类型的时间器调度:
JobSchedule { ????JobSchedule(Type?jobType,?string?cronExpression) ????{ ????????JobType?=?jobType; ????????CronExpression?=?cronExpression; ????}
????public?Type?JobType?{?get;?} ????public?string?CronExpression?{?get;?} }
在这个类中,JobType 是一个作业的类型,例如本例子中的DemoJob 。CronExpression 是一个Cron 的表达式。
Cron表达式允许复杂的计时器调度,所以可以设置规则,比如每个月的5号和20号,在早上8点到10点之间每半小时触发一次。
关于Quartz.NET 的Cron 表达式,可以在这儿查到。
注意:不同操作系统使用的Cron表达式有一定的区别,写表达式的时候一定要注意这一点。
?
然后,我们把作业添加到Startup.ConfigureServices() 中:
ConfigureServices(IServiceCollection?services) { ????services.AddControllers();
????services.AddSingleton<IJobFactory,?SingletonJobFactory>(); ????services.AddSingleton<ISchedulerFactory,?StdSchedulerFactory>();
????services.AddSingleton<DemoJob>(); ????services.AddSingleton(new?JobSchedule( ????????jobType:?typeof(DemoJob), ????????cronExpression:?"0/5?*?*?*?*??"));? }
这段代码向DI添加了四个单例对象:
@H_404_18@SingletonJobFactory,第四节的类,用于创建作业实例;
@H_404_18@ISchedulerFactory的一个实现,是内置的StdSchedulerFactory,用于处理调度和管理作业;
@H_404_18@DemoJob作业本身;
@H_404_18@DemoJob的一个JobSchedule实例,该实例具有每5秒运行一次的Cron表达式。
?
现在,主要代码已经完成,就差Quartz 托管服务了。
六、创建Quartz托管服务
QuartzHostedService 是自己创建的Quartz 托管服务,继承于IHostedService ,用于设置Quartz 调度器,并在后台启动它。
先看一下完整的代码:
QuartzHostedService?:?IHostedService { ????private?readonly?ISchedulerFactory?_schedulerFactory; ????private?readonly?IJobFactory?_jobFactory; ????private?readonly?IEnumerable<JobSchedule>?_jobSchedules;
????QuartzHostedService(ISchedulerFactory?schedulerFactory,?IJobFactory?jobFactory,?IEnumerable<JobSchedule>?jobSchedules) ????{ ????????_schedulerFactory?=?schedulerFactory; ????????_jobSchedules?=?jobSchedules; ????????_jobFactory?=?jobFactory; ????}
????public?IScheduler?Scheduler?{?get;?set;?}
????public?async?Task?StartAsync(CancellationToken?cancellationToken) ????{ ????????Scheduler?=?await?_schedulerFactory.GetScheduler(cancellationToken); ????????Scheduler.JobFactory?=?_jobFactory;?
????????foreach?(var?jobSchedule?in?_jobSchedules) ????????{ ????????????var?job?=?CreateJob(jobSchedule); ????????????var?trigger?=?CreateTrigger(jobSchedule);
????????????await?Scheduler.ScheduleJob(job,?trigger,?cancellationToken); ????????}
????????await?Scheduler.Start(cancellationToken); ????}
????StopAsync(CancellationToken?cancellationToken) ????{ ????????await?Scheduler?.Shutdown(cancellationToken); ????}
????private?ITrigger?CreateTrigger(JobSchedule?schedule) ????{ ????????return?TriggerBuilder ????????.Create() ????????.WithIdentity($"{schedule.JobType.FullName}.trigger") ????????.WithCronSchedule(schedule.CronExpression) ????????.WithDescription(schedule.CronExpression) ????????.Build(); ????}
????private?IJobDetail?CreateJob(JobSchedule?schedule) ????{ ????????var?jobType?=?schedule.JobType; ????????return?JobBuilder ????????????.Create(jobType) ????????????.WithIdentity(jobType.FullName) ????????????.WithDescription(jobType.Name) ????????????.Build(); ????} }
解释一下这段代码:
这段代码中,QuartzHostedService 有三个依赖项:Startup.ConfigureServices() 中注入的ISchedulerFactory 和IJobFactory ,以及一个IEnumerable 。在第五节的代码中,我们只向DI添加了一个JobSchedule ,就是DemoJob 。我们也可以添加多个JobSchedule ,他们都会在这个IEnumerable 中被注入到托管服务中。
StartAsync 在应用程序启动时被调用,它是我们配置Quartz 的地方。我们首先创建IScheduler 的一个实例,为它分配一个属性供以后使用,并将调度程序的JobFactory 设置为注入的实例:
(CancellationToken?cancellationToken) { ????Scheduler?=?await?_schedulerFactory.GetScheduler(cancellationToken); ????Scheduler.JobFactory?=?_jobFactory;? ???? }
然后,循环注入的作业调度,并在类的最后使用CreateJob 和CreateTrigger 方法为每个作业创建一个IJobDetail 和ITrigger 。实际应用中如果有别的需要,也可以通过扩展JobSchedule DTO来定制它。
最后,在调度了所有作业之后,调用Scheduler.Start() 来实际在后台启动Quartz.NET调度器。当应用程序关闭时,框架将调用StopAsync() ,此时可以调用Scheduler.Shutdown() 来安全地关闭调度程序进程。
?
全部完成后,我们启动QuartzHostedService :
(IServiceCollection?services) { ???? ????services.AddHostedService<QuartzHostedService>(); }
?
运行程序,可以看到结果:
demo.DemoJob:?Information:?Demo?! info:?demo.DemoJob[0] ??????Demo?! demo.DemoJob:?Information:?Demo?! info:?demo.DemoJob[0] ??????Demo?! demo.DemoJob:?Information:?Demo?! info:?demo.DemoJob[0] ??????Demo?! demo.DemoJob:?Information:?Demo?! info:?demo.DemoJob[0] ??????Demo?!
本文的代码,在https://github.com/humornif/Demo-Code/tree/master/0029/demo
?
?
|