iT邦幫忙

0

使用Quartz.Net達成Asp.Net Core長時程執行

Web應用程式本身的機制並不適合用來作為執行需要長時程運行的需求,而這類需求卻很常見,而常見的解決方式是

  1. 加長Timeout時間。
  2. 使用Queue的機制於背景執行。

第1種方式並不是一種正確的解決方式,嚴格來說不應該這樣做。
第2種方式很容易達成,但有個缺點,使用者無法得知執行時的狀態,因此無法確認到底是失敗了還是仍然在執行中,這個問題可以透過一些設計解決,但都非常複雜。
目前在Asp.Net Core已經提供了「託管服務」IHostedService,可以解決Web本身不適合做為長時程執行的問題,然而單靠「託管服務」要實現整體的完整機制並不容易,而Quaerz.Net這個老牌的排程程式庫能弭補這個問題,目前新版的Quaerz.Net 3已與Asp.Net Core已有很好的整合。

底下教學目標達成下列需求

  1. 允許Task於背景運行。
  2. 能將執行過程中回報至UI上。

完整的程式可由此下載

初始化Quartz.Net

首先透過nuget將Quartz.Net引用至專案

Install-Package Quartz.AspNetCore

修改Startup.cs加入Quartz.Net的初始化相關程式

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();

            services.AddQuartz(q =>
            {
                q.UseMicrosoftDependencyInjectionScopedJobFactory();
                q.UseSimpleTypeLoader();
                q.UseInMemoryStore();
                q.UseDefaultThreadPool(tp =>
                {
                    tp.MaxConcurrency = 10;
                });

                var jobKey = DemoJob.JobKey;
                q.AddJob<DemoJob>(jobKey, j => j
                    .StoreDurably()
                    .WithDescription("demo job")
                );
            });

            // ASP.NET Core hosting
            services.AddQuartzServer(options =>
            {
                options.WaitForJobsToComplete = true;
            });
        }

這裡為了展示只簡單的使用一些基本配置,想要更深入的了解Quartz.Net可以參考官方的quick start。
上面程式中有個地方須注意因為這邊的DemoJob並沒有關聯到任何的Trigger,Quartz.Net預設情況下會自動刪除因而會出現錯誤,因此要加上StoreDurably()。

撰寫Job

    public class DemoJob : IJob
    {
        public readonly static JobKey JobKey = new JobKey(nameof(DemoJob), JobKey.DefaultGroup);
        public async Task Execute(IJobExecutionContext context)
        {
            await Task.Run(() =>
            {
                for (var i = 0; i < 5; i++)
                {
                    var message = $"第{i}次執行";

                    Console.WriteLine(message);
                    context.Result = message;

                    context.CancellationToken.WaitHandle.WaitOne(TimeSpan.FromSeconds(5));
                }
            }, context.CancellationToken);
        }
    }

DemoJob作為展示,單純的透過Console.WriteLine輸出執行狀態到Asp.Net core的Host上,因此如果使用IIS作為Host畫面上是不會看到任何輸出,所以記得要切換至專案作為Host
https://ithelp.ithome.com.tw/upload/images/20210112/20134249xNS3VuFIu7.jpg
上面程式中的context.Result為Quartz.Net中提供的回報執行狀態機制,後面我們會透過這個參數來取得目前Job中的執行狀態,其物件形態為Object也就是說可以自定義想需要的內容,這邊單純的字串來回報。

新增TaskController與NewTask方法,用來建立Task的Trigger,讓UI觸發執行。

        public async Task<IActionResult> NewTask([FromServices] ISchedulerFactory schedulerFactory, CancellationToken cancellationToken)
        {
            var scheduler = await schedulerFactory.GetScheduler(cancellationToken);

            var trigger = TriggerBuilder.Create()
                .ForJob(DemoJob.JobKey)
                .StartNow()
                .WithSimpleSchedule(x => x.WithRepeatCount(0))
                .Build();

            await scheduler.ScheduleJob(trigger);

            return RedirectToAction("ExecutingJobs");
        }

因為Quartz.Net本身目的是排程功能,而我們希望的Task只是單純的執行一次,因此Trigger使用Simple並且設定WithRepeatCount為0,那麼Trigger執行後即會被Quartz.Net刪除。

最後提供一個UI用來顯示目前執行中的狀態

        public async Task<IActionResult> ExecutingJobs([FromServices] ISchedulerFactory schedulerFactory, CancellationToken cancellationToken)
        {
            var scheduler = await schedulerFactory.GetScheduler();
            var executingJobs = await scheduler.GetCurrentlyExecutingJobs(cancellationToken);

            var data = executingJobs.Select(x => new JobExecutionContextModel(x));
            return View(data);
        }

這樣就簡單快速的完成了一個可以在WEB長時程執行的機制並且能夠有UI反饋
https://ithelp.ithome.com.tw/upload/images/20210112/20134249hWb2KkVKC7.jpg

當然Quartz.Net能做的不止於此,譬如說執行中的Task允許取消機制,實現如Windows排程功能,並且可以讓使用透過UI來操作,集群機制等等。


尚未有邦友留言

立即登入留言