.Net Core 之 (Hangfire 和 Quertz)定时任务
Hangfire
用法比较简单,也直接去官网看。这里直接说几种模式的用法。
项目示例
准备工作
1. 引入nuget包
1 2 3 4 5 | Hangfire.AspNetCore Hangfire.Dashboard.BasicAuthorization #用于Dashboard面板 Hangfire.SqlServer #我这里用的sqlserver,如果用其他的数据库存储就换成对应的扩展包 |
2. appsettings.json中添加配置
1 2 3 4 5 6 7 8 | "HangfireConfig" : { "SslRedirect" : false , "RequireSsl" : false , "LoginCaseSensitive" : false , "Login" : "fcbadmin" , "PasswordClear" : "123456" , "ConnectionString" : "Server=.\\sqlexpress;Database=HangfireTest;Integrated Security=SSPI;" } |
3. Program中添加服务注入配置
1 2 3 4 5 6 7 8 9 10 11 12 13 | builder.Services.AddHangfire(configuration => configuration //.SetDataCompatibilityLevel(CompatibilityLevel.Version_170) .UseSimpleAssemblyNameTypeSerializer() .UseRecommendedSerializerSettings() .UseSqlServerStorage(builder.Configuration.GetSection( "HangfireConfig:ConnectionString" ).Value, new SqlServerStorageOptions { CommandBatchMaxTimeout = TimeSpan.FromMinutes(5), SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5), QueuePollInterval = TimeSpan.Zero, UseRecommendedIsolationLevel = true , DisableGlobalLocks = true })); builder.Services.AddHangfireServer(); |
或者换种简单的
1 2 3 4 | builder.Services.AddHangfire(config => { config.UseStorage( new SqlServerStorage(Configuration.GetSection( "HangfireConfig" ).GetValue< string >( "ConnectionString" ))); }); |
4. Program中添加Dashboard面板,这里添加了账号认证,如果觉得没有必要把配置取消就可以了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | app.UseHangfireDashboard( "/task" , new DashboardOptions { Authorization = new [] { new BasicAuthAuthorizationFilter( new BasicAuthAuthorizationFilterOptions{ SslRedirect = builder.Configuration.GetSection( "HangfireConfig" ).GetValue< bool >( "SslRedirect" ), RequireSsl=builder.Configuration.GetSection( "HangfireConfig" ).GetValue< bool >( "RequireSsl" ), LoginCaseSensitive=builder.Configuration.GetSection( "HangfireConfig" ).GetValue< bool >( "LoginCaseSensitive" ), Users= new []{ new BasicAuthAuthorizationUser{ Login=builder.Configuration.GetSection( "HangfireConfig" ).GetValue< string >( "Login" ), PasswordClear=builder.Configuration.GetSection( "HangfireConfig" ).GetValue< string >( "PasswordClear" ) } } }) }, }); |
准备工作完成了,下面就看几种模式用法
定时任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | /// <summary> /// 执行定时任务。定期作业按指定的 CRON 计划触发多次。 /// </summary> /// <returns></returns> [HttpGet] [Route( "Test_RecurringJob" )] public async Task<IActionResult> Test_RecurringJob() { //自动获取本地时区 RecurringJob.AddOrUpdate<ValuesController>( "测试定时任务" , x => x.TestRecurringJobContent(), Cron.MinuteInterval(10), TimeZoneInfo.Local); ////指定Windows时区 //RecurringJob.AddOrUpdate<ValuesController>("测试定时任务", x => x.TestRecurringJobContent(), Cron.MinuteInterval(2), TimeZoneInfo.CreateCustomTimeZone("China Standard Time", new TimeSpan(08, 00, 00), "China Standard Time", "China Standard Time")); ////指定Linux时区 //RecurringJob.AddOrUpdate<ValuesController>("测试定时任务", x => x.TestRecurringJobContent(), Cron.MinuteInterval(2), TimeZoneInfo.CreateCustomTimeZone("Asia/Shanghai", new TimeSpan(08, 00, 00), "Asia/Shanghai", "Asia/Shanghai")); return Ok( "执行成功" ); } /// <summary> /// 执行定时任务 /// </summary> /// <returns></returns> public async Task<IActionResult> TestRecurringJobContent() { Console.WriteLine( "执行了定时任务" ); return Ok(); } |
一次性作业
1 2 3 4 5 6 7 8 9 10 11 12 | /// <summary> /// 立即执行一次性作业。即发即弃作业仅在创建后立即执行一次。 /// </summary> /// <returns></returns> [HttpGet] [Route( "Test_BackgroundJob_Enqueue" )] public async Task<IActionResult> Test_BackgroundJob_Enqueue() { var jobId = BackgroundJob.Enqueue( () => Console.WriteLine( "执行了立即执行一次性作业" )); return Ok( "执行成功" ); } |
延迟作业
1 2 3 4 5 6 7 8 9 10 11 12 13 | /// <summary> /// 延迟作业。延迟的作业也只执行一次,但不会在一定时间间隔后立即执行。 /// </summary> /// <returns></returns> [HttpGet] [Route( "Test_BackgroundJob_Schedule" )] public async Task<IActionResult> Test_BackgroundJob_Schedule() { var jobId = BackgroundJob.Schedule( () => Console.WriteLine( "执行了延迟作业" ), TimeSpan.FromMinutes(1)); return Ok( "执行成功" ); } |
延续作业
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | /// <summary> /// 延续作业。延续在其父作业完成时执行。 /// </summary> /// <returns></returns> [HttpGet] [Route( "Test_BackgroundJob_ContinueJobWith" )] public async Task<IActionResult> Test_BackgroundJob_ContinueJobWith() { Console.WriteLine( "开始等待,当前时间:" + DateTime.Now); var jobId = BackgroundJob.Schedule( () => Console.WriteLine( "执行了2秒延迟作业,当前时间:" +DateTime.Now), TimeSpan.FromSeconds(2)); BackgroundJob.ContinueJobWith( jobId, () => Console.WriteLine( "在执行了2秒延迟作业之后,又执行了延续作业,当前时间:" + DateTime.Now)); return Ok( "执行成功" ); } |
Quartz
Quartz.Net 官网:https://www.quartz-scheduler.net/
个人感觉没有hangfire好用,使用要稍微复杂一些,官方没有看到类似于hangfire中Dashboard的可视化界面,网上倒是很多人扩展了,可以直接用
核心接口
1 2 3 4 5 6 7 | Scheduler - 与调度程序交互的主要API。 Job - 你想要调度器执行的任务组件需要实现的接口 JobDetail - 用于定义作业的实例。 Trigger(即触发器) - 定义执行给定作业的计划的组件。 JobBuilder - 用于定义/构建 JobDetail 实例,用于定义作业的实例。 TriggerBuilder - 用于定义/构建触发器实例。 Scheduler 的生命期,从 SchedulerFactory 创建它时开始,到 Scheduler 调用shutdown() 方法时结束;Scheduler 被创建后,可以增加、删除和列举 Job 和 Trigger,以及执行其它与调度相关的操作(如暂停 Trigger)。但是,Scheduler 只有在调用 start() 方法后,才会真正地触发 trigger(即执行 job) |
准备工作
1. 引入nuget包
1 | Quartz |
2. Program中添加服务注入配置,这里没有集成数据库
1 | builder.Services.AddScoped<ISchedulerFactory, StdSchedulerFactory>(); |
3. api中依赖注入
1 2 3 4 5 6 7 8 | private readonly ISchedulerFactory _schedulerFactory; private readonly IScheduler _scheduler; public ValuesController( ISchedulerFactory schedulerFactory) { _schedulerFactory = schedulerFactory; _scheduler = _schedulerFactory.GetScheduler().Result; //通过工场类获得调度器 } |
简单使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public async Task<IActionResult> RecurringJob() { //开启调度器 await _scheduler.Start(); //创建触发器(也叫时间策略) var trigger = TriggerBuilder.Create() // .WithSimpleSchedule(x => x.WithIntervalInMinutes(1).RepeatForever())//每分钟执行一次,一直重复执行 //.WithSimpleSchedule(x => x.WithIntervalInSeconds(2).WithRepeatCount(5))//每2秒执行一次,重复执行五次 .WithCronSchedule( "0/2 * * * * ?" ) //使用cron指定运行时间来执行,每2秒运行一次 // .WithIdentity("fcbjob","fcbgroup") .Build(); //创建作业实例 //Jobs即我们需要执行的作业 var jobDetail = JobBuilder.Create<FCBJob>() .WithIdentity( "Myjob" , "fcbgroup" ) //我们给这个作业取了个“Myjob”的名字,取了个组名为“fcbgroup”,这里会通过这两个名字来生成唯一的jobkey,如果不指定会默认生成一个唯一jobkey .Build(); await _scheduler.ScheduleJob(jobDetail, trigger); return Ok( "执行成功" ); } |
注意,这里的FCBJob是一个集成IJob的类
public class FCBJob : IJob { public async Task Execute(IJobExecutionContext context) { Console.WriteLine("Hello Word!"); await Task.CompletedTask; } }
当job中有依赖注入的服务时,需要自定义JobFactory
public class ScopedJobFactory : IJobFactory { private readonly IServiceProvider _serviceProvider; public ScopedJobFactory(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler) { return _serviceProvider.GetRequiredService(bundle.JobDetail.JobType) as IJob; } public void ReturnJob(IJob job) { // 如果你的作业实现了IDisposable,你可以在这里释放它 // 但通常,作业的生命周期由Quartz管理,不需要手动释放 var disposable = job as IDisposable; disposable?.Dispose(); } } public static class QuartzSchedulerService { public async static Task ExecQuartzScheuler(this WebApplication app) { var schedulerFactory=app.Services.GetRequiredService<StdSchedulerFactory>(); IScheduler _scheduler = await schedulerFactory.GetScheduler(); _scheduler.JobFactory = new ScopedJobFactory(app.Services); //重点,这里需要指定我们的JobFactory //开启调度器 await _scheduler.Start(); //创建触发器(也叫时间策略) var trigger = TriggerBuilder.Create() .WithCronSchedule("0/3 * * * * ?") //使用cron指定运行时间来执行,每3秒运行一次 .Build(); //创建作业实例 var jobDetail = JobBuilder.Create<ErrorLogJob>() .WithIdentity("Myjob", "fcbgroup") .Build(); await _scheduler.ScheduleJob(jobDetail, trigger); } } public class ErrorLogJob : IJob { private readonly CloudDataCenterService _cloudDataCenterService; // 假设有个依赖注入的服务 // 依赖注入 public ErrorLogJob(CloudDataCenterService cloudDataCenterService) { _cloudDataCenterService = cloudDataCenterService; } public async Task Execute(IJobExecutionContext context) { Console.WriteLine("11111"); } }
builder.Services.AddSingleton<StdSchedulerFactory>(); builder.Services.AddSingleton<ErrorLogJob>(); var app = builder.Build(); await app.ExecQuartzScheuler();
一个Job执行多个触发器
job和trigger 可以是一对多的关系
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | public async Task<IActionResult> RecurringJobs() { //开启调度器 await _scheduler.Start(); //创建触发器(也叫时间策略) var trigger = TriggerBuilder.Create() .WithCronSchedule( "0/2 * * * * ?" ) //使用cron指定运行时间来执行,每2秒运行一次 .WithIdentity( "fcbjob" , "fcbgroup" ) .Build(); var trigger2 = TriggerBuilder.Create() .WithCronSchedule( "0/1 * * * * ?" ) //使用cron指定运行时间来执行,每2秒运行一次 .WithIdentity( "fcbjob2" , "fcbgroup" ) .Build(); var jobDetail2 = JobBuilder.Create<FCBJob2>() .WithIdentity( "Myjob2" , "fcbgroup" ) .Build(); Dictionary<IJobDetail, IReadOnlyCollection<ITrigger>> keyValuePairs = new Dictionary<IJobDetail, IReadOnlyCollection<ITrigger>>(); keyValuePairs.Add(jobDetail2, new List<ITrigger>() { trigger,trigger2 }); await _scheduler.ScheduleJobs(keyValuePairs, true ); return Ok( "执行成功" ); } |
停止任务调度
1 2 | public async Task<IActionResult> ShutdownScheduler() { await _scheduler.Shutdown(); return Ok(); <br>} |
暂停定时任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public async Task<IActionResult> PauseJob() { //暂停job //JobKey jobKey = new JobKey("Myjob", "fcbgroup"); //if (await _scheduler.CheckExists(jobKey)) //{ // await _scheduler.PauseJob(jobKey); //} //暂停triggerKey TriggerKey triggerKey = new TriggerKey( "fcbjob" , "fcbgroup" ); if (await _scheduler.CheckExists(triggerKey)) { await _scheduler.PauseTrigger(triggerKey); } return Ok(); } |
恢复定时任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public async Task<IActionResult> ResumeJob() { //恢复job //JobKey jobKey = new JobKey("Myjob", "fcbgroup"); //if (await _scheduler.CheckExists(jobKey)) //{ // await _scheduler.ResumeJob(jobKey); //} //恢复triggerKey TriggerKey triggerKey = new TriggerKey( "fcbjob" , "fcbgroup" ); if (await _scheduler.CheckExists(triggerKey)) { await _scheduler.ResumeTrigger(triggerKey); } return Ok(); } |
删除定时任务
1 2 3 4 5 6 7 8 9 | public async Task<IActionResult> DeleteJob() { JobKey jobKey = new JobKey( "Myjob" , "fcbgroup" ); if (await _scheduler.CheckExists(jobKey)) { await _scheduler.DeleteJob(jobKey); } return Ok(); } |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库