天天看點

ASP.NET Core托管運作Quartz.NET作業排程詳解

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

是一個作業的類型,例如本例子中的

DemoJob

CronExpression

是一個

Cron

的表達式。

Cron表達式允許複雜的計時器排程,是以可以設定規則,比如每個月的5号和20号,在早上8點到10點之間每半小時觸發一次。

關于

Quartz.NET

Cron

表達式,可以在這兒查到。

注意:不同作業系統使用的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()

中注入的

ISchedulerFactory

IJobFactory

,以及一個

IEnumerable

。在第五節的代碼中,我們隻向DI添加了一個

JobSchedule

,就是

DemoJob

。我們也可以添加多個

JobSchedule

,他們都會在這個

IEnumerable

中被注入到托管服務中。

StartAsync

在應用程式啟動時被調用,它是我們配置

Quartz

的地方。我們首先建立

IScheduler

的一個執行個體,為它配置設定一個屬性供以後使用,并将排程程式的

JobFactory

設定為注入的執行個體:

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

然後,循環注入的作業排程,并在類的最後使用

CreateJob

CreateTrigger

方法為每個作業建立一個

IJobDetail

ITrigger

。實際應用中如果有别的需要,也可以通過擴充

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

ASP.NET Core托管運作Quartz.NET作業排程詳解

微信公衆号:老王Plus

掃描二維碼,關注個人公衆号,可以第一時間得到最新的個人文章和内容推送

本文版權歸作者所有,轉載請保留此聲明和原文連結