C#服务每周执行函数:Quartz.net示例及替代方案问询
嘿,我懂你的困扰——用每秒轮询的Timer来实现每周任务确实不够优雅,而且网上那些老的Quartz.net示例早就过时了,因为新版本已经全面切换到异步API了。下面给你一个能直接运行的Quartz入门示例,再推荐几个适合初学者的替代方案:
一、Quartz.net 3.x+ 完整入门示例
首先第一步,先安装Quartz的NuGet包:打开NuGet包管理器,搜索Quartz并安装最新稳定版,或者用命令行:
Install-Package Quartz
1. 定义你的任务类
新版本的IJob接口要求Execute方法返回Task(异步),所以我们要写异步的任务逻辑:
using Quartz; using System.Threading.Tasks; // 自定义任务类,实现IJob接口 public class WeeklyTaskJob : IJob { // 异步执行方法,符合新版本Quartz的要求 public async Task Execute(IJobExecutionContext context) { // 这里替换成你需要每周执行的业务逻辑 await Task.Run(() => { System.Console.WriteLine($"每周任务执行成功!当前时间:{System.DateTime.Now:yyyy-MM-dd HH:mm:ss}"); // 调用你的Function1()逻辑 }); } }
2. 配置并启动调度器
接下来写调度器的初始化代码(比如在控制台程序的Main方法,或者Windows/ASP.NET Core服务的启动逻辑里):
using Quartz; using Quartz.Impl; using System; using System.Threading.Tasks; class Program { static async Task Main(string[] args) { // 1. 创建调度器工厂 var schedulerFactory = new StdSchedulerFactory(); // 2. 获取调度器实例 var scheduler = await schedulerFactory.GetScheduler(); // 3. 启动调度器 await scheduler.Start(); // 4. 定义任务:绑定我们的WeeklyTaskJob var job = JobBuilder.Create<WeeklyTaskJob>() .WithIdentity("weeklyJob", "taskGroup") // 给任务设置唯一标识(名称+分组) .Build(); // 5. 定义触发器:设置每周执行的规则,比如每周日凌晨2点 var trigger = TriggerBuilder.Create() .WithIdentity("weeklyTrigger", "taskGroup") // 用内置方法快速定义每周调度,也可以直接写Cron表达式 .WithSchedule(CronScheduleBuilder.WeeklyOnDayAndHourAndMinute(DayOfWeek.Sunday, 2, 0)) // 如果你更熟悉Cron,也可以这样写:.WithCronSchedule("0 0 2 ? * SUN") .Build(); // 6. 将任务和触发器绑定到调度器 await scheduler.ScheduleJob(job, trigger); System.Console.WriteLine("调度器已启动,按任意键停止..."); System.Console.ReadKey(); // 停止调度器 await scheduler.Shutdown(); } }
这段代码的好处是不需要自己做时间判断,Quartz会自动按你设置的规则触发任务,完全替代原来的轮询逻辑。
二、其他适合初学者的优雅替代方案
如果觉得Quartz有点重,还有几个更轻量化的选择:
1. Hangfire
Hangfire是一个超简单的定时任务框架,自带可视化仪表盘,对初学者非常友好。
步骤:
- 安装NuGet包:
Install-Package Hangfire+ 存储包(比如内存存储Install-Package Hangfire.MemoryStorage) - 在ASP.NET Core中配置(控制台也可以用):
// 在Program.cs里配置 var builder = WebApplication.CreateBuilder(args); // 添加Hangfire服务,使用内存存储(生产环境建议用SQL Server等持久化存储) builder.Services.AddHangfire(config => config.UseMemoryStorage()); builder.Services.AddHangfireServer(); var app = builder.Build(); // 启用Hangfire仪表盘(访问/hangfire路径查看任务状态) app.UseHangfireDashboard(); // 调度每周任务:每周日2点执行 RecurringJob.AddOrUpdate("weeklyTask", () => YourWeeklyFunction(), Cron.Weekly(DayOfWeek.Sunday, 2, 0)); app.Run(); // 你的业务方法 public void YourWeeklyFunction() { Console.WriteLine("Hangfire每周任务执行中..."); }
Hangfire的优势是配置简单,还能查看任务执行历史、重试失败任务,非常省心。
2. .NET 内置托管服务 + 异步Timer
如果不想用第三方库,用.NET自带的BackgroundService也能实现优雅的定时任务,避免每秒轮询:
using Microsoft.Extensions.Hosting; using System; using System.Threading; using System.Threading.Tasks; // 自定义托管服务,继承自BackgroundService public class WeeklyHostedService : BackgroundService { private Timer _timer; protected override Task ExecuteAsync(CancellationToken stoppingToken) { // 计算第一次执行的延迟时间(下周日凌晨2点) var nextRunTime = GetNextRunTime(); var initialDelay = nextRunTime - DateTime.Now; if (initialDelay < TimeSpan.Zero) initialDelay += TimeSpan.FromDays(7); // 初始化Timer,只触发一次,执行后再重新计算下一次延迟 _timer = new Timer(async _ => { await ExecuteWeeklyTask(); // 更新下一次执行的延迟 var nextDelay = GetNextRunTime() - DateTime.Now; if (nextDelay < TimeSpan.Zero) nextDelay += TimeSpan.FromDays(7); _timer.Change(nextDelay, Timeout.InfiniteTimeSpan); }, null, initialDelay, Timeout.InfiniteTimeSpan); return Task.CompletedTask; } // 计算下一次执行时间:每周日凌晨2点 private DateTime GetNextRunTime() { var now = DateTime.Now; var daysUntilSunday = ((int)DayOfWeek.Sunday - (int)now.DayOfWeek + 7) % 7; // 如果今天是周日且已经过了2点,就顺延到下周日 if (daysUntilSunday == 0 && now.Hour >= 2) daysUntilSunday = 7; return now.Date.AddDays(daysUntilSunday).AddHours(2); } // 你的每周任务逻辑 private async Task ExecuteWeeklyTask() { Console.WriteLine($"内置托管服务任务执行:{DateTime.Now:yyyy-MM-dd HH:mm:ss}"); await Task.CompletedTask; } // 释放资源 public override void Dispose() { _timer?.Dispose(); base.Dispose(); } }
然后在Program.cs里注册这个服务:
var builder = WebApplication.CreateBuilder(args); builder.Services.AddHostedService<WeeklyHostedService>(); var app = builder.Build(); app.Run();
这个方案完全用.NET原生功能,不需要依赖第三方库,也不会有每秒轮询的问题。
内容的提问来源于stack exchange,提问作者elemer82




