Quartz.NET 计划任务
使用 Quartz.NET 定义任务和触发器,使用 cron 表达式,并与 ASP.NET Core DI 集成。
Quartz.NET 计划任务 是 CoddyKit 上的免费 C# Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 C# Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 C# Academy 课程共包含 4 节课。
为什么选择 Quartz.NET
当您需要 cron 表达式、作业持久化、集群、重试策略和作业链时,Quartz.NET 提供了超越 PeriodicTimer 的生产级调度器。它是广受欢迎的 Java Quartz 调度器的 .NET 移植版本。
安装 Quartz.NET
添加 Quartz 和 Quartz.AspNetCore 包。ASP.NET 集成会自动将调度器注册为托管服务。
# Install packages
dotnet add package Quartz
dotnet add package Quartz.AspNetCore
# Optional: persistence (requires Quartz.Serialization.Json too)
# dotnet add package Quartz.Jobs
# dotnet add package Quartz.Plugins定义作业
实现 IJob(或较新版本 Quartz 中的 IAsyncJob),并提供一个 Execute 方法。作业上下文会提供运行时数据和当前触发器的信息。
using Quartz;
[DisallowConcurrentExecution] // prevent overlapping executions
public class ReportGeneratorJob : IJob
{
private readonly IReportService _reports;
private readonly ILogger<ReportGeneratorJob> _logger;
public ReportGeneratorJob(IReportService r, ILogger<ReportGeneratorJob> l)
{ _reports = r; _logger = l; }
public async Task Execute(IJobExecutionContext context)
{
_logger.LogInformation("Generating report at {Time}", DateTime.UtcNow);
await _reports.GenerateDailyReportAsync(context.CancellationToken);
_logger.LogInformation("Report generated");
}
}在 ASP.NET Core 中注册 Quartz
使用 AddQuartz 配置调度器,并使用 AddQuartzHostedService 将其作为 IHostedService 运行。作业和触发器在 lambda 中配置。
builder.Services.AddQuartz(q =>
{
q.UseMicrosoftDependencyInjectionJobFactory();
var jobKey = new JobKey("ReportGenerator");
q.AddJob<ReportGeneratorJob>(opts => opts.WithIdentity(jobKey));
q.AddTrigger(opts => opts
.ForJob(jobKey)
.WithIdentity("ReportTrigger")
.WithCronSchedule("0 0 8 * * ?") // 8:00 AM daily
);
});
builder.Services.AddQuartzHostedService(q =>
q.WaitForJobsToComplete = true);Cron 表达式语法
Quartz 使用包含 6 或 7 个字段的 cron 表达式:seconds minutes hours day-of-month month day-of-week [year]。Quartz cron 与 Unix cron 略有不同——秒位于第一位。
// Quartz cron format: sec min hour dom month dow [year]
"0 0 8 * * ?" // Every day at 8:00 AM
"0 0/30 9-17 * * ?" // Every 30 min, 9 AM - 5 PM weekdays
"0 0 0 1 * ?" // First day of every month at midnight
"0 0 6 ? * MON-FRI" // Every weekday at 6 AM
"0 0/5 * * * ?" // Every 5 minutes
// Tip: use cronmaker.com or crontab.guru to build expressions简单触发器
对于不需要 cron 复杂性的基于间隔的计划,请使用 WithSimpleSchedule 配置简单触发器。
q.AddTrigger(opts => opts
.ForJob(jobKey)
.WithIdentity("CleanupTrigger")
.StartNow()
.WithSimpleSchedule(s => s
.WithIntervalInHours(1)
.RepeatForever())
);
// Or a one-shot trigger:
q.AddTrigger(opts => opts
.ForJob(jobKey)
.WithIdentity("OneShot")
.StartAt(DateTimeOffset.UtcNow.AddMinutes(5)));作业数据映射
通过 JobDataMap 向作业传递运行时参数。在 Execute 中,可以通过 context.JobDetail.JobDataMap 访问这些参数。
q.AddJob<EmailJob>(opts => opts
.WithIdentity("EmailJob")
.UsingJobData("to", "admin@example.com")
.UsingJobData("subject", "Daily Digest"));
// Inside the job:
public async Task Execute(IJobExecutionContext context)
{
var to = context.JobDetail.JobDataMap.GetString("to");
var subject = context.JobDetail.JobDataMap.GetString("subject");
await _mailer.SendAsync(to!, subject!, "body");
}DisallowConcurrentExecution
[DisallowConcurrentExecution] 可防止在上一个作业实例仍在运行时启动新的实例。对于会访问共享资源的作业,这是必不可少的。
[DisallowConcurrentExecution]
public class InventorySyncJob : IJob
{
// If a sync takes > 1 minute and triggers every minute,
// the next trigger is delayed until this one completes
public async Task Execute(IJobExecutionContext context)
{
await SyncInventoryAsync(context.CancellationToken);
}
}
// Without [DisallowConcurrentExecution], multiple instances
// could run in parallel, causing data conflicts.异常处理与重试
请使用 try/catch 包装作业代码。抛出 JobExecutionException 可以通知 Quartz 立即重新触发作业,或在延迟后重新触发。
public async Task Execute(IJobExecutionContext context)
{
try
{
await ProcessAsync(context.CancellationToken);
}
catch (TransientException ex)
{
// Ask Quartz to retry immediately
var jobException = new JobExecutionException(ex)
{
RefireImmediately = true
};
throw jobException;
}
catch (Exception ex)
{
_logger.LogError(ex, "Job failed — not retrying");
// Don't rethrow — job is done
}
}实际案例:多作业调度器
一个生产环境中的 Quartz 配置,其中包含多个按不同计划运行的作业,并且都通过 DI 使用服务。
builder.Services.AddQuartz(q =>
{
q.UseMicrosoftDependencyInjectionJobFactory();
// Daily report at 8 AM
var reportKey = new JobKey("DailyReport");
q.AddJob<ReportGeneratorJob>(o => o.WithIdentity(reportKey));
q.AddTrigger(o => o.ForJob(reportKey)
.WithCronSchedule("0 0 8 * * ?"));
// Inventory sync every 15 minutes
var syncKey = new JobKey("InventorySync");
q.AddJob<InventorySyncJob>(o => o.WithIdentity(syncKey));
q.AddTrigger(o => o.ForJob(syncKey)
.WithSimpleSchedule(s => s
.WithIntervalInMinutes(15)
.RepeatForever()));
// Cache cleanup every night at 2 AM
var cacheKey = new JobKey("CacheCleanup");
q.AddJob<CacheCleanupJob>(o => o.WithIdentity(cacheKey));
q.AddTrigger(o => o.ForJob(cacheKey)
.WithCronSchedule("0 0 2 * * ?"));
});快速检查
[DisallowConcurrentExecution] 特性在 Quartz.NET 作业上具有什么作用?
回顾:Quartz.NET 计划作业
关键要点:
- Quartz.NET:提供 cron、持久化和集群功能的生产级调度器
- 通过实现带有 Execute 方法的 IJob 来定义作业
- 使用 AddQuartz + AddQuartzHostedService 注册;作业支持 DI
- Cron 格式:seconds minutes hours dom month dow — 请注意秒位于第一位
[DisallowConcurrentExecution]:等待当前执行完成后再重新触发- 使用 JobDataMap 向作业传递运行时参数
常见问题解答
「Quartz.NET 计划任务」课时是免费的吗?
是的 — 「Quartz.NET 计划任务」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 C# Academy 课程的其余内容,请升级到 CoddyKit PRO。 C# Academy 课程共包含 4 节课。
「Quartz.NET 计划任务」这节课中我会学到什么?
使用 Quartz.NET 定义任务和触发器,使用 cron 表达式,并与 ASP.NET Core DI 集成。 你通过在浏览器中直接运行的动手代码来练习 C# Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 C# Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 C# Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「Quartz.NET 计划任务」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 C# Academy 课中编写并运行代码吗?
能。每节 C# Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- IHostedService 与 BackgroundService
- Worker Service 项目
- 周期性任务与计时器
- Quartz.NET 计划任务