只有累積,沒有奇蹟

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 輸入指令
  1. Install-Package Quartz
完畢後可以到 .csproj 專案中查看是否有安裝成功
  1. <ItemGroup>
  2. <PackageReference Include="Quartz" Version="3.0.7" />
  3. </ItemGroup>

建立 IJob & IJobFactory
完成 quartz.net 的安裝動作之後,接下來是設定 Quartz 相關設定與配置,之前在 Quartz.NET 初體驗 中
有介紹過在 Quartz.NET 中有幾個重要的元件像是 Job Schedule,需要客製的話需要實作各自的 interface,第一步先來建立 TestJob 類別,並實作 Execute 方法將我們要處理的內容邏輯寫在裡面,由於是範例代碼就印出現在的時間,另外可以在 TestJob 加上 DisallowConcurrentExecution 預防相同 Job 同時間重複執行
  1. namespace GenericHostLab.Job
  2. {
  3. [DisallowConcurrentExecution]
  4. public class TestJob : IJob
  5. {
  6. private readonly ILogger _logger;
  7.  
  8. public TestJob(ILogger<TestJob> logger)
  9. {
  10. this._logger = logger;
  11. }
  12. public Task Execute(IJobExecutionContext context)
  13. {
  14. _logger.LogInformation($"{DateTime.Now} : TestJob Execute ...");
  15. return Task.CompletedTask;
  16. }
  17. }
  18. }
下一步是建立 SingletonJobFactory 類別,主要作用是實作 IJobFactory 接口並透過工廠方法來確保新增 IJob 是安全的
  1. public class SingletonJobFactory : IJobFactory
  2. {
  3. private readonly IServiceProvider _serviceProvider;
  4. public SingletonJobFactory(IServiceProvider serviceProvider)
  5. {
  6. _serviceProvider = serviceProvider;
  7. }
  8.  
  9. public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
  10. {
  11. return _serviceProvider.GetRequiredService(bundle.JobDetail.JobType) as IJob;
  12. }
  13.  
  14. public void ReturnJob(IJob job) { }
  15. }

建立 Schedule
一般來說會是多個排程同時執行,為了建立 Job 方便建立一個 JobSchedule 類別其中定義了 JobType 與設定 CronJob 排程的執行時間,Cron expressions 表達式可以定義 Job 所需要執行頻率的規則,例如我排程執行的時間/頻率是每 3 秒鐘會執行一次,在 Cron 就使用 0/3 * * * * ? 來表達,這裡推薦 Cron expression 產生器 讓我們可以更方便的產生需要的 CronTrigger
  1. public class JobSchedule
  2. {
  3. public JobSchedule(Type jobType, string cronExpression)
  4. {
  5. JobType = jobType;
  6. CronExpression = cronExpression;
  7. }
  8.  
  9. public Type JobType { get; }
  10. public string CronExpression { get; }
  11. }
接著就是要將 JobSchedule、SingletonJobFactory 在啟動時註冊至 Service中,在 .NET Core DI 內建提供三種服務容器生命週期 (LifeTime) 可提供開發者設定,這裡都使用 Singleton 方式讓使用時在 Application 只會有一份 Instance
  1. .ConfigureServices((hostContext, services) =>
  2. {
  3. services.Configure<HostOptions>(option =>
  4. {
  5. option.ShutdownTimeout = TimeSpan.FromSeconds(30);
  6. });
  7.  
  8. services.AddSingleton<IJobFactory, SingletonJobFactory>();
  9. services.AddSingleton<ISchedulerFactory, StdSchedulerFactory>();
  10.  
  11. var testJob = new JobSchedule(jobType: typeof(TestJob),cronExpression: "0/3 * * * * ?");
  12. services.AddSingleton<TestJob>();
  13. services.AddSingleton(testJob);

實作 IHostedService
接著建立 QuartzHostedService 類別並實作 IHostedService 介面,上一篇有對 IHostedService 做簡單介紹,們可以透過其特性在 QuartzHostedService 類別中定義我們所想要在應用程式或服務啟動與關閉時的工作代碼如下
  1. namespace GenericHostLab.Service
  2. {
  3. public class QuartzHostedService: IHostedService
  4. {
  5. private readonly ISchedulerFactory _schedulerFactory;
  6. private readonly IJobFactory _jobFactory;
  7. private readonly IEnumerable<JobSchedule> _jobSchedules;
  8. private readonly ILogger<QuartzHostedService> _logger;
  9. private IScheduler _scheduler;
  10.  
  11. public QuartzHostedService(ILoggerFactory loggerFactory,
  12. ISchedulerFactory schedulerFactory, IEnumerable<JobSchedule> jobSchedules, IJobFactory jobFactory)
  13. {
  14. _logger = loggerFactory.CreateLogger<QuartzHostedService>();
  15. _schedulerFactory = schedulerFactory;
  16. _jobSchedules = jobSchedules;
  17. _jobFactory = jobFactory;
  18. }
  19. public async Task StartAsync(CancellationToken cancellationToken)
  20. {
  21. _logger.LogInformation("QuartzHostedService Start...");
  22.  
  23. _scheduler = await _schedulerFactory.GetScheduler(cancellationToken);
  24. _scheduler.JobFactory = _jobFactory;
  25.  
  26. foreach (var schedule in _jobSchedules)
  27. {
  28. await _scheduler.ScheduleJob(
  29. JobBuilder
  30. .Create(schedule.JobType)
  31. .WithIdentity(schedule.JobType.FullName)
  32. .WithDescription(schedule.JobType.Name)
  33. .Build(),
  34. TriggerBuilder
  35. .Create()
  36. .WithIdentity($"{schedule.JobType.FullName}.trigger")
  37. .WithCronSchedule(schedule.CronExpression)
  38. .WithDescription(schedule.CronExpression)
  39. .Build()
  40. , cancellationToken);
  41. }
  42.  
  43. await _scheduler.Start(cancellationToken);
  44. }
  45.  
  46. public async Task StopAsync(CancellationToken cancellationToken)
  47. {
  48. _logger.LogInformation("QuartzHostedService Stop...");
  49. await _scheduler?.Shutdown(cancellationToken);
  50. }
  51. }
  52. }
代碼內容首先在建構子使用 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,代碼如下
  1. .ConfigureServices((hostContext, services) =>
  2. {
  3. // 省略
  4. services.AddHostedService<QuartzHostedService>();
  5. }
接著執行應用程式,可以看到應用程式啟動後每三秒在 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 泛型主機

Related Posts:

  • [NETCore] ASP.NET Core 建立排程服務 - 使用 Generic Host 搭配 Quartz.Net - Part 3前言 這是 ASP.NET Core 建立排程服務 - 使用 Generic Host 搭配 Quartz.Net 系列文第三篇,這系列文的目標是使用 ASP.NET Core Generic Host 搭配排程套件 Quartz.Net,程式註冊為 Windows Service 服務執行, 第一篇介紹了 Gereric Host 泛型主機的基本設定,第二篇介紹 ASP.NET Core 與 Quartz.Net 的整合,接下來這一篇… Read More
  • [NETCore] ASP.NET Core 建立排程服務 - 使用 Generic Host 搭配 Quartz.Net - Part 1前言 最近有個需求是固定時間取得特定資料進行修改,在查詢相關資料之後決定使用 ASP.NET Core Generic Host 為出發,在搭配 .NET 中熱門的排程套件 Quartz.Net,測試完畢之後再將程式註冊為 Windows Service 服務就可滿足使用者的需求,這篇文章是整理開發時的重點流程為系列文,給有需要使用 ASP.NET Core 開發排程相關應用程式需求的朋友一些參考,若有問題或是錯誤的地方歡迎各位高手給予… Read More
  • [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 執行,這一篇是重點是在 A… Read More
  • [.NETCore] Windows Service - 服務並未以適時的方式回應啟動或控制請求。前言 在上一篇有提到如何使用指令 註冊 Window Service 服務,提到了如何用指令操作 Windows Service 看啟用的狀態,但有時在啟動時會發生錯誤造成啟動失敗的狀況發生,舉例來說在啟動服務時跳出  'Windows 無法啟動,本機電腦的 TestService 服務,錯誤 1503 : 服務並未已適時的方式回應啟動獲控制請求。 這篇要說明的是在註冊服務當下發生異常的處理方式,若有問題歡… Read More
  • [Windows] 註冊 Windows Service 服務前言 最近專案有個需求要將排程透過 Windows Service 服務來執行,在 Windows OS 要註冊 Service 可以用  cmd  與  powershell  兩種方式來建立以及刪除 Service,兩種方式之前都有使用過但要再使用時都會上網查因此決定紀錄一下未來方便查詢,此篇就針對這兩種方式進行基本介紹與說明,若有問題歡迎提出一起討論或是給予指導… Read More

0 意見:

張貼留言

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

Design by Anders Noren | Blogger Theme by NewBloggerThemes.com