只有累積,沒有奇蹟

2021年9月14日 星期二

[NETCore] ASP.NET Core 建立排程服務 - 使用 Generic Host 搭配 Quartz.Net - Part 2

前言
這是 ASP.NET Core 建立排程服務 - 使用 Generic Host 搭配 Quartz.Net 系列文第二篇,這系列文的目標是使用 ASP.NET Core Generic Host 搭配排程套件 Quartz.Net,程式註冊為 Windows Service 服務執行,
在上一篇介紹了在 main 方法中建立 HostBuilder 並加入 TimeHostedService 執行,這一篇是重點是在 ASP.NET Core 中使用 Quartz.Net 排程套件若有問題或是錯誤的地方歡迎各位高手給予指導
GenericHostLab SampleCode 傳送門 : http://bit.ly/2Y1mqYN
Quartz
上一步建立完 Generic Host 基本設定之後,接下來另一個重點就是使用 Quartz 來建立排程機制,Quartz.Net 是一套功能齊全的工作排程框架,由 Java 熱門的排程框架 Quartz 移植到 .NET 上,open source 且提供彈性的設定讓開發者使用,安裝方式與一般套件相同,首先先到 Nuget Package Manage 搜尋 "quartz",安裝目前最新版的 Quartz.NET 套件
或是在 Nuget Package Console 輸入指令
Install-Package Quartz 
完畢後可以到 .csproj 專案中查看是否有安裝成功
<ItemGroup>
    <PackageReference Include="Quartz" Version="3.0.7" />    
</ItemGroup> 

建立 IJob & IJobFactory
完成 quartz.net 的安裝動作之後,接下來是設定 Quartz 相關設定與配置,之前在 Quartz.NET 初體驗 中
有介紹過在 Quartz.NET 中有幾個重要的元件像是 Job Schedule,需要客製的話需要實作各自的 interface,第一步先來建立 TestJob 類別,並實作 Execute 方法將我們要處理的內容邏輯寫在裡面,由於是範例代碼就印出現在的時間,另外可以在 TestJob 加上 DisallowConcurrentExecution 預防相同 Job 同時間重複執行
namespace GenericHostLab.Job
{
    [DisallowConcurrentExecution]
    public class TestJob : IJob
    {
        private readonly ILogger _logger;

        public TestJob(ILogger<TestJob> logger)
        {
            this._logger = logger;
        }
        public Task Execute(IJobExecutionContext context)
        {
            _logger.LogInformation($"{DateTime.Now} : TestJob Execute ...");
            return Task.CompletedTask;
        }
    }
} 
下一步是建立 SingletonJobFactory 類別,主要作用是實作 IJobFactory 接口並透過工廠方法來確保新增 IJob 是安全的
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) { }
} 

建立 Schedule
一般來說會是多個排程同時執行,為了建立 Job 方便建立一個 JobSchedule 類別其中定義了 JobType 與設定 CronJob 排程的執行時間,Cron expressions 表達式可以定義 Job 所需要執行頻率的規則,例如我排程執行的時間/頻率是每 3 秒鐘會執行一次,在 Cron 就使用 0/3 * * * * ? 來表達,這裡推薦 Cron expression 產生器 讓我們可以更方便的產生需要的 CronTrigger
public class JobSchedule
{
    public JobSchedule(Type jobType, string cronExpression)
    {
        JobType = jobType;
        CronExpression = cronExpression;
    }

    public Type JobType { get; }
    public string CronExpression { get; }
} 
接著就是要將 JobSchedule、SingletonJobFactory 在啟動時註冊至 Service中,在 .NET Core DI 內建提供三種服務容器生命週期 (LifeTime) 可提供開發者設定,這裡都使用 Singleton 方式讓使用時在 Application 只會有一份 Instance
.ConfigureServices((hostContext, services) =>
{
    services.Configure<HostOptions>(option =>
    {
        option.ShutdownTimeout = TimeSpan.FromSeconds(30);
    });

    services.AddSingleton<IJobFactory, SingletonJobFactory>();
    services.AddSingleton<ISchedulerFactory, StdSchedulerFactory>();

    var testJob = new JobSchedule(jobType: typeof(TestJob),cronExpression: "0/3 * * * * ?");
    services.AddSingleton<TestJob>();
    services.AddSingleton(testJob);  

實作 IHostedService
接著建立 QuartzHostedService 類別並實作 IHostedService 介面,上一篇有對 IHostedService 做簡單介紹,們可以透過其特性在 QuartzHostedService 類別中定義我們所想要在應用程式或服務啟動與關閉時的工作代碼如下
namespace GenericHostLab.Service
{
    public class QuartzHostedService: IHostedService
    {
        private readonly ISchedulerFactory _schedulerFactory;
        private readonly IJobFactory _jobFactory;
        private readonly IEnumerable<JobSchedule> _jobSchedules;
        private readonly ILogger<QuartzHostedService> _logger;
        private IScheduler _scheduler;

        public QuartzHostedService(ILoggerFactory loggerFactory,
            ISchedulerFactory schedulerFactory, IEnumerable<JobSchedule> jobSchedules, IJobFactory jobFactory)
        {
            _logger = loggerFactory.CreateLogger<QuartzHostedService>();
            _schedulerFactory = schedulerFactory;
            _jobSchedules = jobSchedules;
            _jobFactory = jobFactory;
        }
        public async Task StartAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("QuartzHostedService Start...");

            _scheduler = await _schedulerFactory.GetScheduler(cancellationToken);
            _scheduler.JobFactory = _jobFactory;

            foreach (var schedule in _jobSchedules)
            {
                await _scheduler.ScheduleJob(
                    JobBuilder
                        .Create(schedule.JobType)
                        .WithIdentity(schedule.JobType.FullName)
                        .WithDescription(schedule.JobType.Name)
                        .Build(),
                    TriggerBuilder
                        .Create()
                        .WithIdentity($"{schedule.JobType.FullName}.trigger")
                        .WithCronSchedule(schedule.CronExpression)
                        .WithDescription(schedule.CronExpression)
                        .Build()
                    , cancellationToken);
            }

            await _scheduler.Start(cancellationToken);
        }

        public async Task StopAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("QuartzHostedService Stop...");
            await _scheduler?.Shutdown(cancellationToken);
        }
    }
}
代碼內容首先在建構子使用 DI 注入 schedulerFactory、jobSchedules 與 jobFactory,QuartzService 重點則是 StartAsync 方法 Quartz 設定也就是在 StartAsync 方法中進行,使用 foreach 將所有要執行的 jobSchedule 逐一加到 _scheduler 中,再透過 _scheduler.Start 來啟動排程;於 StopAsync 方法中加入將 schedule shotdown 停止排程運作的邏輯;另外為了觀察是否正常運作,我在 Start 與 Stop 方法都有加上 log 觀察輸出結果。
最後一步是將 QuartzHostedService 加入 service 中,在 ConfigureServices 使用 AddHostedService 擴充方法將 QuartzHostedService 加入 service,代碼如下
.ConfigureServices((hostContext, services) =>
{
    // 省略
    services.AddHostedService<QuartzHostedService>();
}
接著執行應用程式,可以看到應用程式啟動後每三秒在 console 畫面顯示目前的時間,也可以從 console log 看到運行 QuartzJostedServer Start 的紀錄

以上就完成 Quartz.NET 在 Gereric Host 的應用,大功告成打完收工(?
但之前提到過最後的目標是放在 Windows Service 中執行,因此下一篇將繼續介紹如何將 Generic Host 註冊 Windows Server 的流程與設定,如果這篇有不清楚或是看不懂的地方歡迎提出來一起討論 :)

參考
Implement background tasks in microservices with IHostedService and the BackgroundService class
Creating a Quartz.NET hosted service with ASP.NET Core
.NET 泛型主機

0 意見:

張貼留言

Copyright © m@rcus 學習筆記 | Powered by Blogger

Design by Anders Noren | Blogger Theme by NewBloggerThemes.com