在.NET6中使用配置Quartz.NET定時(shí)任務(wù)并使用IHostedService實(shí)現(xiàn)項(xiàng)目啟動(dòng)自動(dòng)加載任務(wù)
1 簡(jiǎn)介
Quartz.Net是一個(gè)強(qiáng)大、開源、輕量的作業(yè)調(diào)度框架,在平時(shí)的項(xiàng)目開發(fā)當(dāng)中也會(huì)時(shí)不時(shí)的需要運(yùn)用到定時(shí)調(diào)度方面的功能,如果每天需要跑任務(wù)的話,你肯定不會(huì)寫個(gè)while循環(huán),里面進(jìn)行任務(wù)作業(yè)吧,這樣是很耗線程的,很耗資源的。所以就有目前的定時(shí)任務(wù)框架。
2 項(xiàng)目級(jí)配置思路
- 首先第一步我需要配置quartz,數(shù)據(jù)庫(kù)里面有幾十條的任務(wù),有需要運(yùn)行的,有需要暫停的,在項(xiàng)目進(jìn)行更新或者重啟的時(shí)候需要重新加載我設(shè)置的任務(wù)信息,做好日志。
- 支持任務(wù)的頁(yè)面配置,如,任務(wù)的管理,增刪改。執(zhí)行按鈕操作
- 統(tǒng)一的進(jìn)行方法配置,采用調(diào)用api的方式進(jìn)行執(zhí)行任務(wù),我們只需要寫好api就行了,在數(shù)據(jù)庫(kù)配置好url。
3 下載包并在program注入
// 1在api層 下載包 <PackageReference Include="Quartz.AspNetCore" Version="3.5.0" /> //2注入配置 //3 net6中 只有program類,直接在里面Build前面添加以下代碼 builder.Services.AddQuartz(q => { // base quartz scheduler, job and trigger configuration }); // ASP.NET Core hosting builder.Services.AddQuartzServer(options => { // when shutting down we want jobs to complete gracefully options.WaitForJobsToComplete = true; });
4 任務(wù)的幫助類
第一步增加Corn表達(dá)式的幫助類,用于驗(yàn)證Corn輸入是否合格
using Quartz.Impl.Triggers; namespace common { public static class QuartzUtil { /// <summary> /// 驗(yàn)證 Cron 表達(dá)式是否有效 /// </summary> /// <param name="cronExpression"></param> /// <returns></returns> public static bool IsValidExpression(this string cronExpression) { try { var trigger = new CronTriggerImpl(); trigger.CronExpressionString = cronExpression; var date = trigger.ComputeFirstFireTimeUtc(null); return date != null; } catch //(Exception e) { return false; } } } }
第二步 任務(wù)調(diào)度服務(wù)公共類,主要負(fù)責(zé)任務(wù)的運(yùn)行和暫停(復(fù)制代碼,有一些引用的錯(cuò)誤,先不要管)
using Quartz; using Quartz.Impl.Matchers; namespace Common { /// <summary> /// 任務(wù)調(diào)度服務(wù) /// </summary> public class QuartzJobService : ITransientSelfDependency { private readonly ISchedulerFactory _schedulerFactory; private readonly ResultfulApiJobFactory _resultfulApiJobFactory; public QuartzJobService(ISchedulerFactory schedulerFactory, ResultfulApiJobFactory resultfulApiJobFactory) { _schedulerFactory = schedulerFactory; _resultfulApiJobFactory = resultfulApiJobFactory; } /// <summary> /// 開始運(yùn)行一個(gè)調(diào)度器 /// </summary> /// <param name="tasks"></param> /// <returns></returns> public async Task<bool> RunAsync(Z_SyncModules tasks) { //1、通過調(diào)度工廠獲得調(diào)度器 var scheduler = await _schedulerFactory.GetScheduler(); var taskName = $"{tasks.Id}>{tasks.ModuleName}"; //2、創(chuàng)建一個(gè)觸發(fā)器 var trigger = TriggerBuilder.Create() .WithIdentity(taskName, taskName) .StartNow() .WithDescription(tasks.Description) // .WithSimpleSchedule(x => x.WithIntervalInSeconds((int)tasks.Frequency).RepeatForever()) 觸發(fā)表達(dá)式 0 0 0 1 1 ? .WithCronSchedule(tasks.SyncTime) .Build(); //3、創(chuàng)建任務(wù) var jobDetail = JobBuilder.Create<ResultfulApiJob>() .WithIdentity(taskName, taskName) .UsingJobData("TasksId", tasks.Id.ToString()) .Build(); //4、寫入 Job 實(shí)例工廠 解決 Job 中取 ioc 對(duì)象 scheduler.JobFactory = _resultfulApiJobFactory; //5、將觸發(fā)器和任務(wù)器綁定到調(diào)度器中 await scheduler.ScheduleJob(jobDetail, trigger); //6、開啟調(diào)度器 await scheduler.Start(); Console.WriteLine("運(yùn)行成功:" + taskName); return await Task.FromResult(true); } /// <summary> /// 關(guān)閉調(diào)度器 /// </summary> /// <param name="tasks"></param> /// <returns></returns> public async Task<bool> CloseAsync(Z_SyncModules tasks) { IScheduler scheduler = await _schedulerFactory.GetScheduler(); var taskName = $"{tasks.Id}>{tasks.ModuleName}"; var jobKeys = (await scheduler .GetJobKeys(GroupMatcher<JobKey>.GroupEquals(taskName))) .ToList().FirstOrDefault(); if (jobKeys == null ) { MessageBox.Show($"未找到任務(wù):{taskName}"); } var triggers = await scheduler.GetTriggersOfJob(jobKeys); ITrigger trigger = triggers?.Where(x => x.JobKey.Name == taskName).FirstOrDefault(); if (trigger == null) { MessageBox.Show($"未找到觸發(fā)器:{taskName}"); } await scheduler.PauseTrigger(trigger.Key); await scheduler.UnscheduleJob(trigger.Key);// 移除觸發(fā)器 await scheduler.DeleteJob(trigger.JobKey); Console.WriteLine("關(guān)閉成功:"+ taskName); return await Task.FromResult(true); } } }
第三步 運(yùn)行的方法,也就是任務(wù)運(yùn)行的時(shí)候,需要指定一個(gè)類,繼承 IJob,也就是我們都使用這個(gè)類去執(zhí)行方法
using Quartz; using System.Diagnostics; namespace HZY.Quartz.Service.Jobs { /// <summary> /// Resultful 風(fēng)格 Api Job /// </summary> [DisallowConcurrentExecution] public class ResultfulApiJob : IJob { private readonly ApiRequestService _apiRequestService; private readonly IServiceProvider _provider; private readonly ILogger<ResultfulApiJob> _logger; public ResultfulApiJob(ApiRequestService apiRequestService, ILogger<ResultfulApiJob> logger, IServiceProvider provider) { _apiRequestService = apiRequestService; _logger = logger; _provider = provider; } public async Task Execute(IJobExecutionContext context) { try { Stopwatch _stopwatch = new Stopwatch(); _stopwatch.Restart(); var tasksId = context.MergedJobDataMap.GetString("TasksId")?.ToString(); if (string.IsNullOrWhiteSpace(tasksId)) { _logger.LogError("tasksId 空!"); return; } Z_SyncModules tasks = null; using (var scope = _provider.CreateScope()) { // 解析你的作用域服務(wù) var service = scope.ServiceProvider.GetService<IAdminRepository<Z_SyncModules>>(); tasks = await service.SelectNoTracking.FirstOrDefaultAsync(w=>w.Id==Guid.Parse(tasksId)); } if (tasks == null) { _logger.LogError("tasks 空!"); return; } var time = DateTime.Now; var taskId = tasks?.Id ?? Guid.Empty; var text = $"{tasks.ModuleName}|組={tasks.ModuleName}|{time:yyyy-MM-dd}|StartTime={time: HH:mm:ss:fff}|"; var result = await _apiRequestService.RequestAsync("Post", tasks.ApiUrl); if (!result.IsSuccess) { _logger.LogError($"Web Api RequestAsync(); 請(qǐng)求失敗! WebApi 返回結(jié)果:{result.Message}"); } _stopwatch.Stop(); var endTime = $"{DateTime.Now:HH:mm:ss:fff}"; //運(yùn)行結(jié)束記錄 text += $"EndTime={endTime}|{_stopwatch.ElapsedMilliseconds} 毫秒|結(jié)果={result.Message}"; } catch (Exception ex) { var message = $@"Message={ex.Message}\r\n StackTrace={ex.StackTrace}\r\n Source={ex.Source}\r\n"; _logger.LogError(ex, message, null); } } } }
第四步注入IJob實(shí)例,方便注入
using Quartz; using Quartz.Spi; namespace HZY.Services.Admin.QuartzJobTask { /// <summary> /// IJob 對(duì)象無(wú)法構(gòu)造注入 需要此類實(shí)現(xiàn) 返回 注入后得 Job 實(shí)例 /// </summary> public class ResultfulApiJobFactory : IJobFactory { private readonly IServiceProvider _serviceProvider; public ResultfulApiJobFactory(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler) { //Job類型 Type jobType = bundle.JobDetail.JobType; return _serviceProvider.GetService(jobType) as IJob; } public void ReturnJob(IJob job) { var disposable = job as IDisposable; disposable?.Dispose(); } } }
第五步就是webapi的請(qǐng)求,也就是那些post,get請(qǐng)求
我這里直接用的開源框架 Flurl.Http
namespace Common { /// <summary> /// WebApi 請(qǐng)求服務(wù) /// </summary> public class ApiRequestService : ITransientSelfDependency { private readonly ILogger<ApiRequestService> _logger; public ApiRequestService(ILogger<ApiRequestService> logger) { _logger = logger; } /// <summary> /// 請(qǐng)求數(shù)據(jù) /// </summary> /// <param name="requsetMode"></param> /// <param name="apiUrl"></param> /// <param name="headerKeyValue"></param> /// <returns></returns> public async Task<(bool IsSuccess, string Message)> RequestAsync(string requsetMode, string apiUrl, string headerKeyValue = null) { try { var headerKey = "HZY.Quartz.Job.Request"; var headerValue = "Success"; if (!string.IsNullOrWhiteSpace(headerKeyValue) && headerKeyValue.Contains("=")) { headerKey = headerKeyValue.Split('=')[0]; headerValue = headerKeyValue.Split('=')[1]; } IFlurlRequest flurlRequest = apiUrl.WithHeader(headerKey, headerValue); if (flurlRequest == null) { return (false, "flurlRequest 空指針!"); } IFlurlResponse flurResponse = default; if (requsetMode == "Post") { flurResponse = await flurlRequest.PostAsync(); } if (requsetMode == "Get") { flurResponse = await flurlRequest.GetAsync(); } if (flurResponse == null) { return (false, "flurResponse 空指針!"); } var result = await flurResponse.GetStringAsync(); if (string.IsNullOrWhiteSpace(result)) { return (false, "result 空指針!"); } return (true, result); } catch (Exception ex) { _logger.LogError(ex, $"接口請(qǐng)求異?!続piRequestService 》RequestAsync】:{ex.Message}"); return (false, ex.Message); } } } }
5 ioc容器注入
services.AddTransient<ISchedulerFactory, StdSchedulerFactory>(); //Job 實(shí)例化工廠 services.AddSingleton<ResultfulApiJobFactory>(); //Reultful 風(fēng)格 api 請(qǐng)求 任務(wù) services.AddTransient<ResultfulApiJob>();
6 實(shí)現(xiàn)項(xiàng)目啟動(dòng)就從數(shù)據(jù)庫(kù)里面運(yùn)行加載任務(wù)
寫一個(gè) Worker 繼承 IHostedService 里面的邏輯就寫,讀數(shù)據(jù)庫(kù),循環(huán)加載任務(wù),加載任務(wù)的方法就在上面的RUNAsync() using System.Threading; using System.Threading.Tasks; namespace HZY.Quartz { public class Worker : IHostedService { private readonly ILogger<Worker> _logger; private readonly IServiceProvider _provider; private readonly QuartzJobService _quartzJob; public Worker(ILogger<Worker> logger, QuartzJobService quartzJob, IServiceProvider provider) { _logger = logger; this._quartzJob = quartzJob; _provider = provider; } public async Task StartAsync(CancellationToken cancellationToken) { _logger.LogInformation($"程序服務(wù)開始 : {DateTime.Now}"); List<Z_SyncModules> servicelist = new(); using (var scope = _provider.CreateScope()) { // 解析你的作用域服務(wù) var service = scope.ServiceProvider.GetService<xx>(); if (service != null) { servicelist = await service.Where(xx).ToListAsync(); } _logger.LogInformation($"程序服務(wù)查詢定時(shí)任務(wù)列表成功 : {DateTime.Now}"); foreach (var item in servicelist) { //自動(dòng)恢復(fù)任務(wù)機(jī)制a try { var result = await _quartzJob.RunAsync(item); if (result) { _logger.LogInformation($"自動(dòng)開啟任務(wù)成功 [{DateTime.Now}] "); } } catch (Exception ex) { _logger.LogError(ex, $"自動(dòng)開啟任務(wù)錯(cuò)誤 [{DateTime.Now}] : {ex.Message}"); } } } await StopAsync(cancellationToken); //在項(xiàng)目查詢運(yùn)行的時(shí)候運(yùn)行一次 } public Task StopAsync(CancellationToken cancellationToken) { return Task.CompletedTask; } } }
7 總結(jié)
以上就是我之前在項(xiàng)目中使用定時(shí)任務(wù)的一些邏輯和代碼,
當(dāng)然了我還參照了目前的開源項(xiàng)目 https://gitee.com/hzy6/hzy-quartz 一些設(shè)計(jì)思想和代碼
,基本上在項(xiàng)目中可以隨便使用。具體的一些執(zhí)行邏輯就可以自己去寫
8 Corn
Cron表達(dá)式生成器1:https://fontawesome.com
Cron表達(dá)式生成器2:https://cron.qqe2.com/
0 0/60 * * * ? 每60分鐘執(zhí)行一次
0 55 7 * * ? 每天7:55執(zhí)行一次
0 0 1 ? * L 每周一凌晨1點(diǎn)執(zhí)行
0 0 18 18 * ? 每月18號(hào)18點(diǎn)執(zhí)行一次
到此這篇關(guān)于在.NET6中使用配置Quartz.NET定時(shí)任務(wù)并使用IHostedService實(shí)現(xiàn)項(xiàng)目啟動(dòng)自動(dòng)加載任務(wù)的文章就介紹到這了,更多相關(guān).NET6 Quartz.NET定時(shí)任務(wù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
asp.net省市三級(jí)聯(lián)動(dòng)的DropDownList+Ajax的三種框架(aspnet/Jquery/ExtJs)示例
前段時(shí)間需要作一個(gè)的Web前端應(yīng)用,需要用多個(gè)框架,一個(gè)典型的應(yīng)用場(chǎng)景是省市三級(jí)聯(lián)動(dòng),基于此應(yīng)用,特將三種主要的ajax框架略作整理,方便有需要的朋友查閱。2010-06-06asp.net多選項(xiàng)卡頁(yè)面的創(chuàng)建及使用方法
看了很多朋友還不會(huì)創(chuàng)建多選項(xiàng)卡的頁(yè)面,特地總結(jié)了一下用法,看一遍就會(huì)了,感興趣的朋友可以參考下2013-01-01.NET?8新預(yù)覽版使用?Blazor?組件進(jìn)行服務(wù)器端呈現(xiàn)(項(xiàng)目體驗(yàn))
這篇文章主要介紹了.NET?8新預(yù)覽版使用?Blazor?組件進(jìn)行服務(wù)器端呈現(xiàn)(項(xiàng)目體驗(yàn)),這是 Blazor 統(tǒng)一工作的開始,旨在使 Blazor 組件能夠滿足客戶端和服務(wù)器端的所有 Web UI 需求,需要的朋友可以參考下2023-04-04Asp.Net如何將多個(gè)RadioButton指定在一個(gè)組中
將多個(gè)RadioButton指定在一個(gè)組中,實(shí)現(xiàn)其實(shí)很簡(jiǎn)單,一句代碼即可,具體如下,希望對(duì)大家有所幫助2013-12-12ASP.Net中英文復(fù)合檢索文本框?qū)崿F(xiàn)思路及代碼
前段時(shí)間,寫一個(gè)用戶部門的管理頁(yè)面,需要對(duì)后臺(tái)獲取的用戶數(shù)據(jù)實(shí)現(xiàn)英漢檢索功能于是就有了下文,編輯的很詳細(xì)圖文并茂呢?感興趣的你可不要錯(cuò)過了哈,或許本文對(duì)你有所幫助呢2013-02-02ASP.NET 緩存分析和實(shí)踐淺析提高運(yùn)行效率
說到ASP.NET緩存,那就是:盡早緩存;經(jīng)常緩存您應(yīng)該在應(yīng)用程序的每一層都實(shí)現(xiàn)緩存。2010-02-02