前言
排程任務在後端開發裡幾乎無所不在:定期備份資料、產生報表、發送郵件、執行系統維護。Quartz.NET 是 .NET 生態裡最成熟的開源排程框架,提供豐富的功能,也有足夠的彈性應對各種場景。
Quartz.NET 是什麼?
Quartz.NET 是 Java Quartz 排程框架的 .NET 移植版,提供企業級的排程功能:
- 支援複雜的排程模式(Cron 表達式)
- 具備叢集功能,可確保高可用性
- 提供任務持久化
- 彈性的錯誤處理機制
- 豐富的監控與管理功能
基本概念
Quartz.NET 有三個核心元件:
- Job(工作):定義要執行的具體任務
- Trigger(觸發器):決定任務何時執行
- Scheduler(排程器):負責協調 Job 和 Trigger
實作範例
1. 安裝套件
使用 NuGet 安裝 Quartz:
2. 建立 Job
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class HelloJob : IJob { public async Task Execute(IJobExecutionContext context) { string executeTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); await Console.Out.WriteLineAsync($"Hello! 執行時間: {executeTime}"); var dataMap = context.JobDetail.JobDataMap; string jobSays = dataMap.GetString("jobSays"); await Console.Out.WriteLineAsync($"參數值: {jobSays}"); } }
|
3. 設定觸發器並啟動
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 28 29
| IScheduler scheduler = await StdSchedulerFactory.GetDefaultScheduler();
IJobDetail job = JobBuilder.Create<HelloJob>() .WithIdentity("helloJob", "group1") .UsingJobData("jobSays", "Hello World!") .Build();
ITrigger trigger = TriggerBuilder.Create() .WithIdentity("trigger1", "group1") .StartNow() .WithSimpleSchedule(x => x .WithIntervalInSeconds(5) .RepeatForever()) .Build();
await scheduler.ScheduleJob(job, trigger);
await scheduler.Start();
await Task.Delay(TimeSpan.FromSeconds(60));
await scheduler.Shutdown();
|
4. 使用 Cron 表達式
Cron 表達式適合需要精細排程的場景:
1 2 3 4 5 6 7 8
| ITrigger cronTrigger = TriggerBuilder.Create() .WithIdentity("trigger2", "group1") .WithCronSchedule("0 0 9 * * ?") .Build();
await scheduler.ScheduleJob(job, cronTrigger);
|
Cron 表達式格式(共 7 欄,年可省略):
| 欄位 |
範圍 |
範例 |
| 秒 |
0–59 |
0 |
| 分 |
0–59 |
30 |
| 時 |
0–23 |
9 |
| 日 |
1–31 |
* |
| 月 |
1–12 |
* |
| 星期 |
1–7 |
? |
| 年(選填) |
1970–2099 |
— |
常用範例:
0 0 9 * * ? — 每天早上 9:00 執行
0 0/30 * * * ? — 每 30 分鐘執行一次
0 0 9-17 * * ? — 每天 09:00–17:00,整點各執行一次
5. 錯誤處理
Quartz.NET 本身不會自動重試失敗的 Job,需要在 Execute 內自行處理。最常見的做法是捕捉例外、記錄日誌後,透過 JobExecutionException 告知排程器是否要立即重新執行:
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 28 29 30 31 32
| public class ErrorHandlingJob : IJob { private readonly ILogger<ErrorHandlingJob> _logger;
public ErrorHandlingJob(ILogger<ErrorHandlingJob> logger) { _logger = logger; }
public async Task Execute(IJobExecutionContext context) { try { await DoWorkAsync(); } catch (Exception ex) { _logger.LogError(ex, "Job {JobKey} 執行失敗", context.JobDetail.Key);
var jobEx = new JobExecutionException(ex, refireImmediately: false); throw jobEx; } }
private async Task DoWorkAsync() { await Task.CompletedTask; } }
|
實務注意事項
Job 設計
- 盡量讓每個 Job 只做一件事,避免在單次執行中混入太多業務邏輯
- 長時間運行的 Job 記得處理
CancellationToken(透過 context.CancellationToken),讓排程器能安全中斷
- 如果同一 Job 可能被多個執行緒同時觸發,加上
[DisallowConcurrentExecution] 屬性
執行緒池與頻率
- 預設執行緒池大小為 10,高並行場景要視需求調整(
quartz.threadPool.maxConcurrency)
- 過高頻率的排程(如每秒數次)適合考慮換成訊息佇列,避免 Job Store 成為瓶頸
日誌與可觀測性
- Job 內建議注入
ILogger,用結構化日誌記錄執行時間與關鍵參數,方便事後追查
- 生產環境可搭配
IJobListener 集中監聽所有 Job 的執行結果,不用每個 Job 各自重複寫
小結
這篇涵蓋了 Quartz.NET 最核心的用法:建 Job、設 Trigger、用 Cron 表達式,以及正確丟出 JobExecutionException。入門夠用了。進階方向包括持久化 Job Store(搭配資料庫)、叢集模式、以及 IJobListener/ITriggerListener 做監控,官方文件有完整範例。
參考資源