使用ASP.NET Core创建Quartz.NET托管服务

简介-什么是Quartz.NET?

根据他们的网站:

Quartz.NET是功能齐全的开源作业调度系统,可用于最小的应用程序到大型企业系统。

它是许多ASP.NET开发人员的主要资料,被用作以可靠的群集方式在计时器上运行后台任务的方式。将Quartz.NET与ASP.NET Core一起使用非常相似-Quartz.NET支持.NET Standard 2.0,因此您可以轻松地在应用程序中使用它。

“通用主机”也可以使用这种非HTTP方案,但是由于种种原因,我目前通常不使用那些。希望通过在这些非HTTP方案中进行额外的投资,可以在ASP.NET Core 3.0中改善这一点。

虽然可以创建“定时”后台服务(例如,每10分钟运行一次任务),但Quartz.NET提供了更为强大的解决方案。通过使用Cron触发器,您可以确保任务仅在一天的特定时间(例如,凌晨2:30)运行,或仅在特定的几天运行,或任意组合运行。它还允许您以集群方式运行应用程序的多个实例,以便在任何时候只能运行一个实例。

在本文中,我将介绍创建Quartz.NET作业并将其调度为在托管服务中的计时器上运行的基础知识。

安装Quartz.NET

Quartz.NET是一个.NET Standard 2.0 NuGet软件包,因此应该易于安装在您的应用程序中。对于此测试,我创建了一个ASP.NET Core项目并选择了Empty模板。您可以使用安装Quartz.NET软件包dotnet add package Quartz。如果您查看该项目的.csproj,它应该看起来像这样:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp2.2</TargetFramework>
    <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.App" />
    <PackageReference Include="Quartz" Version="3.0.7" />
  </ItemGroup>

</Project>

创建一个IJob

对于我们正在安排的实际后台工作,我们将使用“ hello world”实现,该实现写入并ILogger<>(进而写入控制台)。您应该实现IJob包含单个异步Execute()方法的Quartz接口。请注意,这里我们使用依赖注入将记录器注入到构造函数中。

using Microsoft.Extensions.Logging;
using Quartz;
using System.Threading.Tasks;

[DisallowConcurrentExecution]
public class HelloWorldJob : IJob
{
    private readonly ILogger<HelloWorldJob> _logger;
    public HelloWorldJob(ILogger<HelloWorldJob> logger)
    {
        _logger = logger;
    }

    public Task Execute(IJobExecutionContext context)
    {
        _logger.LogInformation("Hello world!");
        return Task.CompletedTask;
    }
}

我还用[DisallowConcurrentExecution]属性装饰了工作。该属性可防止Quartz.NET尝试同时运行同一作业。

DisallowConcurrentExecution

  • 此标记用在实现Job的类上面,意思是不允许并发执行,按照我之前的理解是 不允许调度框架在同一时刻调用Job类,后来经过测试发现并不是这样,而是Job(任务)的执行时间[比如需要10秒]大于任务的时间间隔[Interval(5秒)],那么默认情况下,调度框架为了能让
    任务按照我们预定的时间间隔执行,会马上启用新的线程执行任务。否则的话会等待任务执行完毕以后
    再重新执行!(这样会导致任务的执行不是按照我们预先定义的时间间隔执行)
  • 测试代码,这是官方提供的例子。设定的时间间隔为3秒,但job执行时间是5秒,设置@DisallowConcurrentExecution以后程序会等任务执行完毕以后再去执行,否则会在3秒时再启用新的线程执行

创建一个IJobFactory

接下来,我们需要告诉Quartz如何创建的实例IJob。默认情况下,Quartz将使用Activator.CreateInstance有效地调用尝试并“更新”作业实例new HelloWorldJob()。不幸的是,由于我们正在使用构造函数注入,因此无法正常工作。相反,我们可以提供一个IJobFactory挂钩到ASP.NET Core依赖项注入容器(IServiceProvider)的自定义:

using Microsoft.Extensions.DependencyInjection;
using Quartz;
using Quartz.Spi;
using System;

public class SingletonJobFactory : IJobFactory
{
    private readonly IServiceScope _serviceScope;
    public SingletonJobFactory(IServiceProvider serviceProvider)
    {
         _serviceScope = serviceProvider.CreateScope();
    }

    public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
    {
        return _serviceScope.ServiceProvider.GetRequiredService(bundle.JobDetail.JobType) as IJob;
    }

    public void ReturnJob(IJob job) 
    {
    	(job as IDisposable)?.Dispose();
    }
}

该工厂将一个IServiceProviderin作为构造函数,并实现IJobFactory接口。重要的方法是NewJob()工厂必须返回IJobQuartz调度程序所请求的方法。在此实现中,我们直接委派给IServiceProvider,并让容器找到所需的实例。由于IJob必须在非通用版本中GetRequiredService返回,因此必须在结尾处强制转换为object

ReturnJob方法是调度程序尝试返回(即销毁)工厂创建的作业的地方。不幸的是,没有内置的机制可以做到这一点IServiceProvider。我们无法创建IScopeService适合所需的Quartz API的新产品,因此我们只能创建单例作业。

这个很重要。使用上述实现,仅创建Singletons(或transient)的IJob实现是安全的。

配置作业

我在IJob这里仅显示一个实现,但是我们希望Quartz托管服务是适用于任何数量作业的通用实现。为了解决这个问题,我们创建了一个简单的DTO JobSchedule,用于定义给定作业类型的计时器计划:

using System;

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

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

JobType是该作业的.NET类型(HelloWorldJob在我们的例子),并且CronExpression是一个Quartz.NET的Cron表达。Cron表达式允许复杂的计时器调度,因此您可以设置规则,例如“每月5号和20号在上午8点至10点之间每半小时触发一次”。只需查看示例文档即可,因为并非所有由不同系统使用的Cron表达式都是可以互换的。

我们将作业添加到DI并在中配置其时间表Startup.ConfigureServices()

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Quartz;
using Quartz.Impl;
using Quartz.Spi;

public void ConfigureServices(IServiceCollection services)
{
    // Add Quartz services
    services.AddSingleton<IJobFactory, SingletonJobFactory>();

    // Add our job
    services.AddSingleton<HelloWorldJob>();
    services.AddSingleton(new JobSchedule(
        jobType: typeof(HelloWorldJob),
        cronExpression: "0/5 * * * * ?")); // run every 5 seconds
}

此代码将四个内容作为单例添加到DI容器:

  • SingletonJobFactory前面所示,用于创建工作情况。
  • 实现ISchedulerFactory,内置的StdSchedulerFactory,它可以处理调度和管理作业
  • HelloWorldJob工作本身
  • 实例JobScheduleHelloWorldJob与Cron表达式每5秒运行一次。

现在只需要将它们组合在一起QuartzHostedService

创建QuartzHostedService

QuartzHostedService是的实现IHostedService,设置了Quartz调度程序,并启动它在后台运行。由于Quartz的设计,我们可以IHostedService直接实现,而不是基于BackgroundService类派生的更常见的方法。该服务的完整代码在下面列出,稍后我将讨论。

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Quartz;
using Quartz.Spi;

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

    public QuartzHostedService(IJobFactory jobFactory,
        IEnumerable<JobSchedule> jobSchedules)
    {
        _jobSchedules = jobSchedules;
        _jobFactory = jobFactory;
    }
    public IScheduler Scheduler { get; set; }

    public async Task StartAsync(CancellationToken cancellationToken)
    {
        Scheduler = await StdSchedulerFactory.GetDefaultScheduler(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 static IJobDetail CreateJob(JobSchedule schedule)
    {
        var jobType = schedule.JobType;
        return JobBuilder
            .Create(jobType)
            .WithIdentity(jobType.FullName)
            .WithDescription(jobType.Name)
            .Build();
    }

    private static ITrigger CreateTrigger(JobSchedule schedule)
    {
        return TriggerBuilder
            .Create()
            .WithIdentity($"{schedule.JobType.FullName}.trigger")
            .WithCronSchedule(schedule.CronExpression)
            .WithDescription(schedule.CronExpression)
            .Build();
    }
}

您可以使用AddHostedService()扩展方法在中注册托管服务Startup.ConfigureServices

public void ConfigureServices(IServiceCollection services)
{
    // ...
    services.AddHostedService<QuartzHostedService>();
}

如果运行该应用程序,则应该看到每隔5秒运行一次后台任务并写入控制台(或配置日志记录的任何地方)
使用ASP.NET Core创建Quartz.NET托管服务_第1张图片

你可能感兴趣的:(Asp.Net,Core)