在.NET6中使用配置Quartz.NET定時任務(wù)并使用IHostedService實現(xiàn)項目啟動自動加載任務(wù)
1 簡介
Quartz.Net是一個強大、開源、輕量的作業(yè)調(diào)度框架,在平時的項目開發(fā)當中也會時不時的需要運用到定時調(diào)度方面的功能,如果每天需要跑任務(wù)的話,你肯定不會寫個while循環(huán),里面進行任務(wù)作業(yè)吧,這樣是很耗線程的,很耗資源的。所以就有目前的定時任務(wù)框架。
2 項目級配置思路
- 首先第一步我需要配置quartz,數(shù)據(jù)庫里面有幾十條的任務(wù),有需要運行的,有需要暫停的,在項目進行更新或者重啟的時候需要重新加載我設(shè)置的任務(wù)信息,做好日志。
- 支持任務(wù)的頁面配置,如,任務(wù)的管理,增刪改。執(zhí)行按鈕操作
- 統(tǒng)一的進行方法配置,采用調(diào)用api的方式進行執(zhí)行任務(wù),我們只需要寫好api就行了,在數(shù)據(jù)庫配置好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表達式的幫助類,用于驗證Corn輸入是否合格
using Quartz.Impl.Triggers;
namespace common
{
public static class QuartzUtil
{
/// <summary>
/// 驗證 Cron 表達式是否有效
/// </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ù)公共類,主要負責任務(wù)的運行和暫停(復(fù)制代碼,有一些引用的錯誤,先不要管)
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>
/// 開始運行一個調(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)建一個觸發(fā)器
var trigger = TriggerBuilder.Create()
.WithIdentity(taskName, taskName)
.StartNow()
.WithDescription(tasks.Description)
// .WithSimpleSchedule(x => x.WithIntervalInSeconds((int)tasks.Frequency).RepeatForever())
觸發(fā)表達式 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 實例工廠 解決 Job 中取 ioc 對象
scheduler.JobFactory = _resultfulApiJobFactory;
//5、將觸發(fā)器和任務(wù)器綁定到調(diào)度器中
await scheduler.ScheduleJob(jobDetail, trigger);
//6、開啟調(diào)度器
await scheduler.Start();
Console.WriteLine("運行成功:" + 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);
}
}
}第三步 運行的方法,也就是任務(wù)運行的時候,需要指定一個類,繼承 IJob,也就是我們都使用這個類去執(zhí)行方法
using Quartz;
using System.Diagnostics;
namespace HZY.Quartz.Service.Jobs
{
/// <summary>
/// Resultful 風格 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(); 請求失敗! WebApi 返回結(jié)果:{result.Message}");
}
_stopwatch.Stop();
var endTime = $"{DateTime.Now:HH:mm:ss:fff}";
//運行結(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實例,方便注入
using Quartz;
using Quartz.Spi;
namespace HZY.Services.Admin.QuartzJobTask
{
/// <summary>
/// IJob 對象無法構(gòu)造注入 需要此類實現(xiàn) 返回 注入后得 Job 實例
/// </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的請求,也就是那些post,get請求
我這里直接用的開源框架 Flurl.Http
namespace Common
{
/// <summary>
/// WebApi 請求服務(wù)
/// </summary>
public class ApiRequestService : ITransientSelfDependency
{
private readonly ILogger<ApiRequestService> _logger;
public ApiRequestService(ILogger<ApiRequestService> logger)
{
_logger = logger;
}
/// <summary>
/// 請求數(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, $"接口請求異?!続piRequestService 》RequestAsync】:{ex.Message}");
return (false, ex.Message);
}
}
}
}5 ioc容器注入
services.AddTransient<ISchedulerFactory, StdSchedulerFactory>();
//Job 實例化工廠
services.AddSingleton<ResultfulApiJobFactory>();
//Reultful 風格 api 請求 任務(wù)
services.AddTransient<ResultfulApiJob>();6 實現(xiàn)項目啟動就從數(shù)據(jù)庫里面運行加載任務(wù)
寫一個 Worker 繼承 IHostedService
里面的邏輯就寫,讀數(shù)據(jù)庫,循環(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ù)查詢定時任務(wù)列表成功 : {DateTime.Now}");
foreach (var item in servicelist)
{
//自動恢復(fù)任務(wù)機制a
try
{
var result = await _quartzJob.RunAsync(item);
if (result)
{
_logger.LogInformation($"自動開啟任務(wù)成功 [{DateTime.Now}] ");
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"自動開啟任務(wù)錯誤 [{DateTime.Now}] : {ex.Message}");
}
}
}
await StopAsync(cancellationToken); //在項目查詢運行的時候運行一次
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
}7 總結(jié)
以上就是我之前在項目中使用定時任務(wù)的一些邏輯和代碼,
當然了我還參照了目前的開源項目 https://gitee.com/hzy6/hzy-quartz 一些設(shè)計思想和代碼
,基本上在項目中可以隨便使用。具體的一些執(zhí)行邏輯就可以自己去寫
8 Corn
Cron表達式生成器1:https://fontawesome.com
Cron表達式生成器2:https://cron.qqe2.com/
0 0/60 * * * ? 每60分鐘執(zhí)行一次
0 55 7 * * ? 每天7:55執(zhí)行一次
0 0 1 ? * L 每周一凌晨1點執(zhí)行
0 0 18 18 * ? 每月18號18點執(zhí)行一次
到此這篇關(guān)于在.NET6中使用配置Quartz.NET定時任務(wù)并使用IHostedService實現(xiàn)項目啟動自動加載任務(wù)的文章就介紹到這了,更多相關(guān).NET6 Quartz.NET定時任務(wù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
asp.net省市三級聯(lián)動的DropDownList+Ajax的三種框架(aspnet/Jquery/ExtJs)示例
前段時間需要作一個的Web前端應(yīng)用,需要用多個框架,一個典型的應(yīng)用場景是省市三級聯(lián)動,基于此應(yīng)用,特將三種主要的ajax框架略作整理,方便有需要的朋友查閱。2010-06-06
.NET?8新預(yù)覽版使用?Blazor?組件進行服務(wù)器端呈現(xiàn)(項目體驗)
這篇文章主要介紹了.NET?8新預(yù)覽版使用?Blazor?組件進行服務(wù)器端呈現(xiàn)(項目體驗),這是 Blazor 統(tǒng)一工作的開始,旨在使 Blazor 組件能夠滿足客戶端和服務(wù)器端的所有 Web UI 需求,需要的朋友可以參考下2023-04-04
Asp.Net如何將多個RadioButton指定在一個組中
將多個RadioButton指定在一個組中,實現(xiàn)其實很簡單,一句代碼即可,具體如下,希望對大家有所幫助2013-12-12
ASP.Net中英文復(fù)合檢索文本框?qū)崿F(xiàn)思路及代碼
前段時間,寫一個用戶部門的管理頁面,需要對后臺獲取的用戶數(shù)據(jù)實現(xiàn)英漢檢索功能于是就有了下文,編輯的很詳細圖文并茂呢?感興趣的你可不要錯過了哈,或許本文對你有所幫助呢2013-02-02

