在 ASP.NET Core和Worker Service中使用Quartz.Net

如今有了一個官方包Quartz.Extensions.Hosting實現使用Quartz.Net運行後臺任務,因此把Quartz.Net添加到ASP.NET Core或Worker Service要簡單得多。json

我將展現如何把Quartz.Net HostedService添加到你的應用,如何建立一個簡單的IJob,以及如何註冊它與trigger。app

簡介——什麼是Quartz.Net異步

Quartz.Net是一個功能齊全的開源做業調度系統,能夠在最小規模的應用程序到大型企業系統使用。函數

有許多ASP.NET的釘子戶,他們以一種可靠的、集羣的方式在定時器上運行後臺任務。使用在ASP.NET Core中使用的Quartz.Net支持了.NET Standar 2.0,所以你能夠輕鬆地在應用程序中使用它。測試

Quartz.Net有三個主要概念:ui

  1. job。這是你想要運行的後臺任務。
  2. trigger。trigger控制job什麼時候運行,一般按某種調度規則觸發。
  3. scheduler。它負責協調job和trigger,根據trigger的要求執行job。

ASP.NET Core很好地支持經過hosted services(託管服務)運行「後臺任務」。當你的ASP.NET Core應用程序啓動,託管服務也啓動,並在應用程序的生命週期中在後臺運行。Quartz.Net 3.2.0經過Quartz.Extensions.Hosting引入了對該模式的直接支持。Quartz.Extensions.Hosting便可以用在ASP.NET Core應用程序,也能夠用在基於「通用主機」的Worker Service。this

雖然能夠建立一個「定時」後臺服務(例如,每10分鐘運行一個任務),但Quartz.NET提供了一個更加健壯的解決方案。經過使用Cron trigger,你能夠確保任務只在一天的特定時間(例如凌晨2:30)運行,或者只在特定的日子運行,或者這些時間的任意組合運行。Quartz.Net還容許你以集羣的方式運行應用程序的多個實例,以便在任什麼時候候只有一個實例能夠運行給定的任務。編碼

Quartz.Net託管服務負責Quartz的調度。它將在應用程序的後臺運行,檢查正在執行的觸發器,並在必要時運行相關的做業。你須要配置調度程序,但不須要擔憂啓動或中止它,IHostedService會爲你管理。spa

在這篇文章中,我將展現建立Quartz.Net job的基礎知識。並將其調度到託管服務中的定時器上運行。.net

安裝Quartz.Net

Quartz.Net是一個.NET Standar 2.0的NuGet包,因此它很容易安裝在你的應用程序中。對於這個測試,我建立了一個Worker Service項目。你能夠經過使用dotnet add package Quartz.Extensions.Hosting命令安裝Quartz.Net託管包。若是你查看項目的.csproj,它應該是這樣的:

<Project Sdk="Microsoft.NET.Sdk.Worker">
  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
    <UserSecretsId>dotnet-QuartzWorkerService-9D4BFFBE-BE06-4490-AE8B-8AF1466778FD</UserSecretsId>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
    <PackageReference Include="Quartz.Extensions.Hosting" Version="3.2.3" />
  </ItemGroup>
</Project>

這將添加託管服務包,從而引入Quartz.Net。接下來,咱們須要在應用程序中註冊Quartz.Net的服務和 IHostedService。

添加Quartz.Net託管服務

註冊Quartz.Net須要作兩件事:

  1. 註冊Quartz.Net須要的DI容器服務。
  2. 註冊託管服務。

在ASP.NET Core中,一般會在Startup.ConfigureServices()方法中完成這兩項操做。Worker Services不使用Startup類,因此咱們在Program.cs中的IHostBuilder的ConfigureServices方法中註冊它們:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureServices((hostContext, services) =>
            {
                // Add the required Quartz.NET services
                services.AddQuartz(q =>
                {
                    // Use a Scoped container to create jobs. I'll touch on this later
                    q.UseMicrosoftDependencyInjectionScopedJobFactory();
                });

                // Add the Quartz.NET hosted service

                services.AddQuartzHostedService(
                    q => q.WaitForJobsToComplete = true);

                // other config
            });
}

這裏有幾個有趣的點:

  1. UseMicrosoftDependencyInjectionScopedJobFactory:它告訴Quartz.NET註冊一個IJobFactory,該IJobFactory經過從DI容器中建立job。方法中的Scoped部分意味着你的做業可使用scoped服務,而不只僅是single或transient服務。
  2. WaitForJobsToComplete:此設置確保當請求關閉時,Quartz.NET在退出以前等待做業優雅地結束。

若是你如今運行應用程序,將看到Quartz服務啓動,並將大量日誌轉儲到控制檯:

info: Quartz.Core.SchedulerSignalerImpl[0]
      Initialized Scheduler Signaller of type: Quartz.Core.SchedulerSignalerImpl
info: Quartz.Core.QuartzScheduler[0]
      Quartz Scheduler v.3.2.3.0 created.
info: Quartz.Core.QuartzScheduler[0]
      JobFactory set to: Quartz.Simpl.MicrosoftDependencyInjectionJobFactory
info: Quartz.Simpl.RAMJobStore[0]
      RAMJobStore initialized.
info: Quartz.Core.QuartzScheduler[0]
      Scheduler meta-data: Quartz Scheduler (v3.2.3.0) 'QuartzScheduler' with instanceId 'NON_CLUSTERED'
  Scheduler class: 'Quartz.Core.QuartzScheduler' - running locally.
  NOT STARTED.
  Currently in standby mode.
  Number of jobs executed: 0
  Using thread pool 'Quartz.Simpl.DefaultThreadPool' - with 10 threads.
  Using job-store 'Quartz.Simpl.RAMJobStore' - which does not support persistence. and is not clustered.

info: Quartz.Impl.StdSchedulerFactory[0]
      Quartz scheduler 'QuartzScheduler' initialized
info: Quartz.Impl.StdSchedulerFactory[0]
      Quartz scheduler version: 3.2.3.0
info: Quartz.Core.QuartzScheduler[0]
      Scheduler QuartzScheduler_$_NON_CLUSTERED started.
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down....

此時,你已經讓Quartz做爲託管服務在你的應用程序中運行,可是沒有任何job讓它運行。在下一節中,咱們將建立並註冊一個簡單的job。

建立job

對於咱們正在調度的實際後臺工做,咱們將使用一個"hello world"實現它寫入一個ILogger<T>。你應該實現Quartz.NET的接口IJob,它包含一個異步的Execute()方法。注意,咱們在這裏使用依賴注入將日誌程序注入到構造函數中。

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]屬性裝飾了job。此屬性防止Quartz.NET試圖同時運行相同的做業。

如今咱們已經建立了做業,咱們須要將它與trigger一塊兒註冊到DI容器中。

配置job

Quartz.NET爲運行job提供了一些簡單的schedule,但最多見的方法之一是使用Quartz.NET Cron表達式。Cron表達式容許複雜的計時器調度,因此你能夠設置規則,好比「每月的5號和20號,在早上8點到10點之間每半小時觸發一次」。使用時請確保檢查示例文檔,由於不一樣系統使用的全部Cron表達式都是可互換的。

下面的示例展現瞭如何使用每5秒運行一次的trggier來註冊HelloWorldJob:

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureServices((hostContext, services) =>
        {
            services.AddQuartz(q =>  
            {
                q.UseMicrosoftDependencyInjectionScopedJobFactory();

                // Create a "key" for the job
                var jobKey = new JobKey("HelloWorldJob");

                // Register the job with the DI container
                q.AddJob<HelloWorldJob>(opts => opts.WithIdentity(jobKey));

                // Create a trigger for the job
                q.AddTrigger(opts => opts
                    .ForJob(jobKey) // link to the HelloWorldJob
                    .WithIdentity("HelloWorldJob-trigger") // give the trigger a unique name
                    .WithCronSchedule("0/5 * * * * ?")); // run every 5 seconds

            });
            services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);
            // ...
        });

在本代碼中,咱們:

  1. 爲job建立惟一的JobKey。這用於將job與其trggier鏈接在一塊兒。還有其餘鏈接job和trggier的方法,但我認爲這和其餘方法同樣好。
  2. 用AddJob<T>註冊HelloWorldJob。這作了兩件事—它將HelloWorldJob添加到DI容器中,這樣就能夠建立它;它在內部向Quartz註冊job。
  3. 添加一個觸發器,每5秒運行一次做業。咱們使用JobKey將trigger與一個job關聯起來,併爲trigger提供惟一的名稱(在本例中不是必需的,但若是你在集羣模式下運行quartz,這很重要)。最後,咱們爲trigger設置了Cron調度,使做業每5秒運行一次。

這就實現了功能!再也不須要建立自定義的IJobFactory,也不用擔憂是否支持scoped的服務。

默認的包爲你處理全部這些問題——你能夠在IJob中使用scoped的服務,它們將在job完成時被刪除。

若是你如今運行你的應用程序,你會看到和之前同樣的啓動消息,而後每5秒你會看到HelloWorldJob寫入控制檯:

這就是啓動和運行所需的所有內容,可是根據個人喜愛,在ConfigureServices方法中添加了太多內容。你也不太可能想在應用程序中硬編碼做業調度。例如,若是將其提取到配置中,能夠在每一個環境中使用不一樣的調度。

最起碼,咱們但願將Cron調度提取到配置中。例如,你能夠在appsettings.json中添加如下內容:

{
  "Quartz": {
    "HelloWorldJob": "0/5 * * * * ?"
  }
}

而後,你能夠輕鬆地在不一樣環境中覆蓋HelloWorldJob的觸發器調度。

爲了方便註冊,咱們能夠建立一個擴展方法來封裝在Quartz上註冊IJob,並設置它的trigger調度。這段代碼與前面的示例基本相同,可是它使用job的名稱在IConfiguration中加載Cron調度。

public static class ServiceCollectionQuartzConfiguratorExtensions{
    public static void AddJobAndTrigger<T>(
        this IServiceCollectionQuartzConfigurator quartz,
        IConfiguration config)
        where T : IJob
    {
        // Use the name of the IJob as the appsettings.json key
        string jobName = typeof(T).Name;

        // Try and load the schedule from configuration
        var configKey = $"Quartz:{jobName}";
        var cronSchedule = config[configKey];

        // Some minor validation
        if (string.IsNullOrEmpty(cronSchedule))
        {
            throw new Exception($"No Quartz.NET Cron schedule found for job in configuration at {configKey}");
        }

        // register the job as before
        var jobKey = new JobKey(jobName);
        quartz.AddJob<T>(opts => opts.WithIdentity(jobKey));

        quartz.AddTrigger(opts => opts
            .ForJob(jobKey)
            .WithIdentity(jobName + "-trigger")
            .WithCronSchedule(cronSchedule)); // use the schedule from configuration
    }
}

如今咱們可使用擴展方法清理應用程序的Program.cs:

public class Program
{
    public static void Main(string[] args) => CreateHostBuilder(args).Build().Run();

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureServices((hostContext, services) =>
            {
                services.AddQuartz(q =>
                {
                    q.UseMicrosoftDependencyInjectionScopedJobFactory();

                    // Register the job, loading the schedule from configuration
                    q.AddJobAndTrigger<HelloWorldJob>(hostContext.Configuration);
                });

                services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);
            });
}

這本質上與咱們的配置相同,可是咱們已經使添加新job和調度的細節移到配置中變得更容易。

再次運行應用程序會給出相同的輸出:job每5秒寫一次輸出。

總結

在這篇文章中,我介紹了Quartz.NET並展現瞭如何使用新的Quartz.Extensions.Hosting輕鬆添加一個ASP.NET Core託管服務運行Quartz調度器。我展現瞭如何使用trigger實現一個簡單的job,以及如何將其註冊到應用程序中,以便託管的服務按計劃運行它。

 歡迎關注個人公衆號,若是你有喜歡的外文技術文章,能夠經過公衆號留言推薦給我。

原文連接:https://andrewlock.net/using-quartz-net-with-asp-net-core-and-worker-services/

相關文章
相關標籤/搜索