跳到主要内容

Quartz.NET 工作排程指南

用途:.NET 的企業級工作排程框架,支援 Cron 表達式、持久化排程、叢集

NuGetQuartz(核心)/ Quartz.Extensions.Hosting(DI 整合)

授權:Apache-2.0

支援:.NET 6+、.NET Framework 4.6.2+

GitHubquartznet/quartznet


什麼是 Quartz.NET

Quartz.NET 是從 Java 的 Quartz Scheduler 移植過來的 .NET 排程框架。它讓你可以用 Cron 表達式 或時間間隔定義「什麼時候執行什麼任務」,支援排程持久化(應用程式重啟後排程自動恢復)和叢集模式。

在設備控制的場景中,有很多工作是需要定期自動執行的:資料備份、報表產出、資料清理、校驗提醒。手寫 Timer 能做到基本功能,但一旦需要 Cron 排程、錯過補執行、重啟恢復等進階功能,就需要一個正式的排程框架。


vs Hangfire vs BackgroundService vs Timer

比較項目Quartz.NETHangfireBackgroundServiceSystem.Timers.Timer
Cron 表達式✅ 原生支援✅ 支援❌ 需自己算❌ 只有間隔
持久化排程✅ 支援多種 DB✅ 需要 DB❌ 不支援❌ 不支援
Web Dashboard❌ 需第三方✅ 內建❌ 無❌ 無
叢集支援✅ 內建✅ 內建❌ 不支援❌ 不支援
適合場景桌面 + 伺服器Web App簡單背景服務簡單計時
外部依賴可選(記憶體即可)必須 DB
錯過處理✅ Misfire 策略✅ 自動重試❌ 直接跳過❌ 直接跳過
公司場景選擇
  • WPF 桌面應用:Quartz.NET(不需要 Web Dashboard,但需要 Cron 排程)
  • 簡單的定時輪詢(每 N 秒 poll PLC):BackgroundServiceTimer 就夠了
  • Web API 的背景工作:Hangfire(有 Dashboard 監控)

核心概念

概念說明
IScheduler排程器,負責管理所有 Job 和 Trigger 的執行
IJob工作介面,實作 Execute(IJobExecutionContext) 方法
IJobDetailJob 的描述(身份、型別、資料),由 JobBuilder 建立
ITrigger觸發器,定義何時執行 Job,由 TriggerBuilder 建立
CronExpressionCron 表達式,精確到秒的排程描述
JobDataMap附加在 Job 或 Trigger 上的 Key-Value 資料

運作流程

IScheduler
├── IJobDetail (什麼工作) ← JobBuilder.Create<MyJob>()
│ └── JobDataMap (附加資料)
└── ITrigger (什麼時候) ← TriggerBuilder.Create().WithCronSchedule("...")
└── CronExpression / SimpleSchedule

Scheduler → Trigger → Job 時序

上面的結構只描述「誰跟誰有關係」,下圖則展示「執行期怎麼跑」。從註冊排程、等待觸發時點、執行 Job、到結果回寫 JobStore 的完整過程:

關鍵點:

  • Scheduler 是中心:Job 不直接認識 Trigger,由 Scheduler 依 Trigger 時點呼叫對應 Job
  • JobStore 決定持久化:記憶體模式(預設)重啟即丟失;DB 模式重啟後自動接手未完成的 Trigger
  • Misfire 處理:若錯過觸發時點(系統忙、停機),Trigger 狀態會進入 MISFIRED,Quartz 依策略決定跳過或補執行(見下節狀態機)

Trigger 狀態機

每個 ITrigger 在執行期都有內部狀態。下圖是常見的轉換路徑(簡化版,實際 DB 模式會有更多中間狀態):

常用 Misfire 策略(設定於 TriggerBuilder):

  • WithMisfireHandlingInstructionDoNothing():忽略錯過的時點,等下次
  • WithMisfireHandlingInstructionFireAndProceed():立刻補執行一次,後續依原排程
  • WithMisfireHandlingInstructionIgnoreMisfires():補執行所有錯過的時點(可能會一次跑很多次)

安裝

# 核心套件
dotnet add package Quartz

# DI 整合 + HostedService
dotnet add package Quartz.Extensions.Hosting

# 持久化(可選,依需求選擇)
dotnet add package Quartz.Serialization.Json

Cron 表達式速查表

Quartz Cron 表達式有 7 個欄位(秒 分 時 日 月 週 年),其中年是可選的:

秒  分  時  日  月  週  [年]
* * * * * ?
表達式意思
0/30 * * * * ?每 30 秒
0 0/5 * * * ?每 5 分鐘
0 0 * * * ?每小時整點
0 0 8 * * ?每天早上 8:00
0 0 0 * * ?每天午夜
0 0 2 * * ?每天凌晨 2:00(適合備份)
0 0 8 ? * MON每週一早上 8:00
0 0 8 ? * MON-FRI每週一到五早上 8:00
0 0 0 1 * ?每月 1 日午夜
0 0 0 L * ?每月最後一天午夜
特殊字元
  • *:所有值
  • ?:不指定(日和週互斥,其中一個用 ?
  • /:遞增(0/5 = 從 0 開始每 5)
  • L:最後(L 在日欄位 = 月最後一天)
  • MON-FRI:範圍

基本範例

// 1. 定義 Job
public class DatabaseBackupJob : IJob
{
private readonly ILogger<DatabaseBackupJob> _logger;
private readonly IBackupService _backupService;

public DatabaseBackupJob(
ILogger<DatabaseBackupJob> logger,
IBackupService backupService)
{
_logger = logger;
_backupService = backupService;
}

public async Task Execute(IJobExecutionContext context)
{
_logger.LogInformation("Database backup job started");

try
{
await _backupService.BackupAsync();
_logger.LogInformation("Database backup completed successfully");
}
catch (Exception ex)
{
_logger.LogError(ex, "Database backup failed");
// 拋出 JobExecutionException 讓 Quartz 知道執行失敗
throw new JobExecutionException(ex, refireImmediately: false);
}
}
}

// 2. 設定排程(Program.cs / Host Builder)
services.AddQuartz(q =>
{
var jobKey = new JobKey("DatabaseBackup", "Maintenance");

q.AddJob<DatabaseBackupJob>(opts => opts
.WithIdentity(jobKey)
.WithDescription("每日凌晨 2 點備份資料庫"));

q.AddTrigger(opts => opts
.ForJob(jobKey)
.WithIdentity("DatabaseBackup-Trigger")
.WithCronSchedule("0 0 2 * * ?") // 每天凌晨 2:00
.WithDescription("Daily at 2 AM"));
});

// 3. 啟動排程器為背景服務
services.AddQuartzHostedService(opts =>
{
opts.WaitForJobsToComplete = true; // 關機時等待執行中的 Job 完成
});

延伸閱讀