在.NET6中使用配置Quartz.NET定時(shí)任務(wù)并使用IHostedService實(shí)現(xiàn)項(xiàng)目啟動(dòng)自動(dòng)加載任務(wù)
1 簡(jiǎn)介
Quartz.Net是一個(gè)強(qiáng)大、開(kāi)源、輕量的作業(yè)調(diào)度框架,在平時(shí)的項(xiàng)目開(kāi)發(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>
/// 開(kāi)始運(yùn)行一個(gè)調(diào)度器
/// </summary>
/// <param name="tasks"></param>
/// <returns></returns>
public async Task<bool> RunAsync(Z_SyncModules tasks)
{
//1、通過(guò)調(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、開(kāi)啟調(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)求
我這里直接用的開(kāi)源框架 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ù)開(kāi)始 : {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)開(kāi)啟任務(wù)成功 [{DateTime.Now}] ");
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"自動(dòng)開(kāi)啟任務(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)然了我還參照了目前的開(kāi)源項(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)文章
VS2015開(kāi)發(fā)環(huán)境的安裝和配置
本文主要是給大家把C#開(kāi)發(fā)環(huán)境各種版本的更新和安裝過(guò)程重新整理一下貢獻(xiàn)出來(lái),希望對(duì)小伙伴們能夠有所幫助2016-07-07
asp.net下獲取遠(yuǎn)程網(wǎng)頁(yè)的內(nèi)容之二(downmoon原創(chuàng))
asp.net下獲取遠(yuǎn)程網(wǎng)頁(yè)的內(nèi)容之二(downmoon原創(chuàng))...2007-04-04
asp.net 操作XML 按指定格式寫入XML數(shù)據(jù) WriteXml
從SQL下載數(shù)據(jù)到本地為XML文件2009-07-07
asp.net生成靜態(tài)后冗余代碼,去掉viewstate生成的代碼
asp.net生成的viewstate垃圾信息超過(guò)了20K,如果頁(yè)面前二K不是內(nèi)容會(huì)被引擎處罰,所以我們需要清理下asp.net生成靜態(tài)后冗余代碼2012-10-10
.NET?Core類庫(kù)項(xiàng)目中讀取appsettings.json配置的方法
ASP.NET?Core是一個(gè)全新的Web開(kāi)發(fā)平臺(tái),微軟在它上面構(gòu)建了MVC、SingalR、GRPC、Orleans這樣廣泛使用的Web框架,今天通過(guò)本文給大家詳細(xì)介紹下.NET?Core讀取appsettings.json配置的方法,感興趣的朋友一起看看吧2022-03-03
.Net?Core授權(quán)認(rèn)證方案JWT(JSON?Web?Token)初探
這篇文章介紹了.Net?Core授權(quán)認(rèn)證方案JWT,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06

