在.NET Core 中使用Quartz.NET

Quartz.NET是功能齊全的開源作業調度系統,可用於最小的應用程序到大型企業系統。

Quartz.NET具有三個主要概念:

  • job:運行的後臺任務
  • trigger:控制後臺任務運行的觸發器。
  • scheduler:協調job和trigger

ASP.NET Core通過託管服務對運行“後臺任務”具有良好的支持,託管服務在ASP.NET Core應用程序啓動時啓動,並在應用程序生存期內在後臺運行,Quartz.NET版本3.2.0通過Quartz.Extensions.Hosting包引入了對該模式的直接支持,Quartz.Extensions.Hosting可以與ASP.NET Core應用程序一起使用,也可以與基於“通用主機”的工作程序服務一起使用。

雖然.NET Core可以創建“定時”後臺服務(例如,每10分鐘運行一次任務),但Quartz.NET提供了更爲強大的解決方案, 通過使用Cron表達式,您可以確保任務在特定時間(例如,凌晨2:30)運行,或僅在特定的幾天運行,或這些時間的任意組合。Quartz.NET還允許您以集羣方式運行應用程序的多個實例,以便在任何時候都只能運行一個實例。

安裝Quartz.NET

Quartz.NET是一個.NET Standard 2.0 NuGet軟件包,所以大部分項目都是支持的,你可以運行安裝命令,dotnet add package Quartz.Extensions.Hosting,或者在NNuget可視化安裝,如果查看該項目的.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服務和Quartz 。

添加Quartz.NET hosted service

修改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) =>
            {
                // 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
            });
}

UseMicrosoftDependencyInjectionScopedJobFactory(),這個地方告訴Quartz.NET註冊一個IJobFactory,然後從DI容器中獲取Job,這樣也可以使用 Scoped 類型的服務。

WaitForJobsToComplete():當程序關閉時,此設置可確保Quartz.NET在退出之前等待Job正常結束。

如果現在運行您的應用程序,您將看到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。

創建一個IJob

這個地方我創建一個簡單的服務,並且我可以從構造函數中獲取服務。

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嘗試同時運行同一個作業。

設置Job

這個地方通常使用Cron表達式,來設置job的執行時間。

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);
            // ...
        });

現在運行應用程序,您將看到和以前相同的啓動消息,然後每隔5秒鐘就會看到HelloWorldJob寫入控制檯的信息:

將配置提取到appsettings.json

一般情況,我們都不會把cron表達式寫死在代碼中,一般是設置在appsettings.json中

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

爲了更簡單的註冊服務,這個地方我簡單做了一個封裝,這樣也更靈活。

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每5秒輸入一次信息。

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

最後

歡迎掃碼關注我們的公衆號 【全球技術精選】,專注國外優秀博客的翻譯和開源項目分享,也可以添加QQ羣 897216102

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章