Quartz.NET这么NB的作业调度系统,不会还行?

今天介绍一下Quartz.NET的托管运行,官网传送门

一、前言

Quartz.NET,按官网上的说法,是一款功能齐全的任务调度系统,从小型应用到大型企业级系统都能适用。在众多项目中,Quartz.NET以可靠、集群的方式,被用作在定时器上运行后台任务的一种方式。

Quartz.NET主要完成两个方面的内容:

  1. 基于时间计划的后台作业;
  2. 基于因时间计划的触发的任务运行。

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的依赖注入容器挂钩。

public class SingletonJobFactory : IJobFactory
{
    private readonly IServiceProvider _serviceProvider;
    public 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,并使用它来定义一个给定作业类型的时间器调度:

public class JobSchedule
{
    public JobSchedule(Type jobType, string cronExpression)
    {
        JobType = jobType;
        CronExpression = cronExpression;
    }

    public Type JobType { get; }
    public string CronExpression { get; }
}

在这个类中,JobType是一个作业的类型,例如本例子中的DemoJobCronExpression是一个Cron的表达式。

Cron表达式允许复杂的计时器调度,所以可以设置规则,比如每个月的5号和20号,在早上8点到10点之间每半小时触发一次。

关于Quartz.NETCron表达式,可以在这儿查到。

注意:不同操作系统使用的Cron表达式有一定的区别,写表达式的时候一定要注意这一点。

然后,我们把作业添加到Startup.ConfigureServices()中:

public void 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 * * * * ?")); // 每5秒运行一次
}

这段代码向DI添加了四个单例对象:

  1. SingletonJobFactory,第四节的类,用于创建作业实例;
  2. ISchedulerFactory的一个实现,是内置的StdSchedulerFactory,用于处理调度和管理作业;
  3. DemoJob作业本身;
  4. DemoJob的一个JobSchedule实例,该实例具有每5秒运行一次的Cron表达式。

现在,主要代码已经完成,就差Quartz托管服务了。

六、创建Quartz托管服务

QuartzHostedService是自己创建的Quartz托管服务,继承于IHostedService,用于设置Quartz调度器,并在后台启动它。

先看一下完整的代码:

public class QuartzHostedService : IHostedService
{
    private readonly ISchedulerFactory _schedulerFactory;
    private readonly IJobFactory _jobFactory;
    private readonly IEnumerable<JobSchedule> _jobSchedules;

    public 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);
    }

    public async Task 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()中注入的ISchedulerFactoryIJobFactory,以及一个IEnumerable。在第五节的代码中,我们只向DI添加了一个JobSchedule,就是DemoJob。我们也可以添加多个JobSchedule,他们都会在这个IEnumerable中被注入到托管服务中。

StartAsync在应用程序启动时被调用,它是我们配置Quartz的地方。我们首先创建IScheduler的一个实例,为它分配一个属性供以后使用,并将调度程序的JobFactory设置为注入的实例:

public async Task StartAsync(CancellationToken cancellationToken)
{
    Scheduler = await _schedulerFactory.GetScheduler(cancellationToken);
    Scheduler.JobFactory = _jobFactory; 
    //... 
}

然后,循环注入的作业调度,并在类的最后使用CreateJobCreateTrigger方法为每个作业创建一个IJobDetailITrigger。实际应用中如果有别的需要,也可以通过扩展JobSchedule DTO来定制它。

最后,在调度了所有作业之后,调用Scheduler.Start()来实际在后台启动Quartz.NET调度器。当应用程序关闭时,框架将调用StopAsync(),此时可以调用Scheduler.Shutdown()来安全地关闭调度程序进程。

全部完成后,我们启动QuartzHostedService

public void ConfigureServices(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


微信公众号:老王Plus

扫描二维码,关注个人公众号,可以第一时间得到最新的个人文章和内容推送

本文版权归作者所有,转载请保留此声明和原文链接

ASP.NET Core托管运行Quartz.NET作业调度详解的更多相关文章

  1. ASP.NET Core MVC 源码学习:详解 Action 的激活

    前言 在 上一篇 文章中,我们已经学习了 ASP.NET Core MVC 的启动流程,那么 MVC 在启动了之后,当请求到达过来的时候,它是怎么样处理的呢? 又是怎么样把我们的请求准确的传达到我们的 ...

  2. ASP.NET Core MVC 源码学习:详解 Action 的匹配

    前言 在 上一篇 文章中,我们已经学习了 ASP.NET Core MVC 的启动流程,那么 MVC 在启动了之后,当请求到达过来的时候,它是怎么样处理的呢? 又是怎么样把我们的请求准确的传达到我们的 ...

  3. DevExpress ASP.NET Core Controls v18.2新功能详解

    行业领先的.NET界面控件2018年第二次重大更新——DevExpress v18.2日前正式发布,本站将以连载的形式为大家介绍新版本新功能.本文将介绍了DevExpress ASP.NET Core ...

  4. Asp.Net Core 中IdentityServer4 实战之 Claim详解

    一.前言 由于疫情原因,让我开始了以博客的方式来学习和分享技术(持续分享的过程也是自己学习成长的过程),同时也让更多的初学者学习到相关知识,如果我的文章中有分析不到位的地方,还请大家多多指教:以后我会 ...

  5. ASP.NET Core托管和部署Linux实操演练手册

    一.课程介绍 ASP.NET Core 是一种全新的跨平台开源 .NET 框架,能够在 IIS.Nginx.Apache.Docker 上进行托管或在自己的进程中进行自托管. 作为一个.NET Web ...

  6. 【ASP.NET Core】运行原理之启动WebHost

    ASP.NET Core运行原理之启动WebHost 本节将分析WebHost.CreateDefaultBuilder(args).UseStartup<Startup>().Build ...

  7. 【ASP.NET Core】运行原理[3]:认证

    本节将分析Authentication 源代码参考.NET Core 2.0.0 HttpAbstractions Security 目录 认证 AddAuthentication IAuthenti ...

  8. 【ASP.NET Core】运行原理(4):授权

    本系列将分析ASP.NET Core运行原理 [ASP.NET Core]运行原理(1):创建WebHost [ASP.NET Core]运行原理(2):启动WebHost [ASP.NET Core ...

  9. ASP.NET CORE下运行CMD命令

    ASP.NET CORE下运行CMD命令,用以前的ASP.NET 的命令System.Diagnostics.Process.Start("notepad");这样是可以运行出记事 ...

随机推荐

  1. JS 数组, 对象的增查改删(多语法对比)

    数据结构横向对比, 增, 查, 改, 删 建议: 在用数据结构的时候, 优先考虑Map和Set(考虑数据的唯一性), 放弃传统的数组和Object, 特别是比较复杂的数据结构时 数组 Map与Arra ...

  2. netty学习心得1

    也不是系统学习,工作需求,一点点抠的,需要自己笼统学习下. 首先功能实现: serverBootstrap.group(boss, work).channel(NioServerSocketChann ...

  3. vue 项目中实时请求接口 建立长连接

    需求:在项目中需要每隔五秒请求一次接口 第一种方法:直接在mounted钩子函数中处理 mounted() { window.setInterval(() => { setTimeout(thi ...

  4. 利用Z.Expressions.Eval表达式求值

    Z.Expression.Eval是一个开源的(OpenSource),可扩展的(Extensible),超轻量级(Super lightweight)的公式化语言解析执行工具包. 使用方法:1.从n ...

  5. Swiper 在IE9 及其他浏览器使用

    Swiper 在IE9 及其他浏览器使用 前言 昨天遇到一个问题,swiper 使用版本是3.4.2 除了Ie9浏览器外其他浏览器都正常,IE9 无法轮播,执行控制台报错源码问题.没办法,只能降级兼容 ...

  6. [安洵杯 2019]iamthinking&&thinkphp6.0反序列化漏洞

    [安洵杯 2019]iamthinking&&thinkphp6.0反序列化漏洞 刚开始是403,扫描以下目录,扫描到三个目录. [18:06:19] 200 - 1KB - /REA ...

  7. Anaconda, conda, pyenv, virtualenv的区别

    1.Python环境 Python解释器--Python.exe Python包集合--Lib,包括自带包和第三方包 2.Anaconda--一个科学计算环境,Python的发行版本 包括了Conda ...

  8. Windows下设置Mongodb用户名密码

    MongoDB认证: 在默认的情况下,Mongodb是监听在127.0.0.1 IP上的,端口号默认为27017,任何客户端都可以连接,不需要认证 默认情况下,Mongodb也是没有管理账户的,除非你 ...

  9. Linux服务器内存监控—每小时检查&超出发送邮件&重启占用最高的Java程式

    简介与优点 使用该脚本能自行判断系统内存使用情况是否超出设定百分比 能在超出预警值时执行重启程式的操作 能记录重启过程,并将具体LOG邮件发送给指定收信人 可以设定Crontab排程,达成每隔一段时间 ...

  10. vue项目中视频播放结束返回首页出现1秒左右的白屏问题

    vue项目的性能优化问题,一直以来都是大家比较关注的. 近日负责的项目中,使用了SignalR实时通讯,客户端中点击发起播放视频的请求到服务器,服务器接到请求后再调用前端的播放视频方法,以此来达到播放 ...