簡介
Cronos 是一個專為 .NET 設計的輕量級、高性能 cron 表達式解析庫,由 Hangfire 團隊開發。相比其他 cron 庫,它專注於提供精確的時區處理和高效的計算算法,特別適合需要跨時區調度的現代應用。
- 在
.NET應用中需要使用Cron表達式驅動定時任務時,系統自帶的定時器並不支持直接解析Cron語法。 - 雖然有
Quartz.NET等重量級調度框架,但在只需表達式解析與下一次執行時間計算的場景中,引入整套框架過於臃腫。 Cronos是一款專注於Cron表達式解析與下次執行時間計算的輕量級庫,支持標準與擴展語法、時區、秒級精度,並且性能優越、易於集成。
解決的關鍵痛點
- 精確時區轉換:原生支持從任意時區計算觸發時間
- 無狀態解析:線程安全的表達式解析
- 高效算法:O(1) 時間複雜度的下次觸發計算
- 輕量級:無外部依賴,適合
Serverless環境
安裝與配置
NuGet安裝
Install-Package Cronos
- 命名空間引用
using Cronos;
Cronos 支持 .NET Standard 2.0+,兼容所有現代 .NET 平台(.NET Core/.NET 5+/Mono 等)。
核心功能
-
多種表達式格式
- 標準 5 段(分鐘、小時、日、月、周)
- 擴展 6 段(秒、分鐘、小時、日、月、周)
- 可選年字段(7 段:秒、分、時、日、月、周、年)
-
下次與多次執行時間計算
GetNextOccurrence(DateTime baseTime, TimeZoneInfo tz = null)GetOccurrences(DateTime start, DateTime end, TimeZoneInfo tz = null)
-
時區支持
- 可傳入
TimeZoneInfo,自動根據時區與夏令時規則計算本地時間
- 可傳入
-
異常反饋
- 對非法表達式拋出
CronExpressionException,內含錯誤位置提示
- 對非法表達式拋出
-
線程安全
CronExpression實例可在多線程間共享
-
性能
- 解析與計算採用高效算法,適合高併發環境
主要 API 用法
// 1. 解析表達式
CronExpression expr = CronExpression.Parse("*/15 8-18 * * MON-FRI", CronFormat.Standard);
// 2. 獲取下一次執行時間(本地時區)
DateTime? next = expr.GetNextOccurrence(DateTime.Now);
// 3. 獲取下一次執行時間(指定時區)
var tz = TimeZoneInfo.FindSystemTimeZoneById("China Standard Time");
DateTime? nextInCst = expr.GetNextOccurrence(DateTime.UtcNow, tz);
// 4. 枚舉一段時間內所有執行時間
DateTime start = DateTime.Now;
DateTime end = start.AddDays(1);
IEnumerable<DateTime> occs = expr.GetOccurrences(start, end);
// 5. 捕獲非法表達式異常
try {
CronExpression.Parse("invalid cron");
} catch (CronFormatException ex) {
Console.WriteLine($"表達式語法錯誤:{ex.Message}");
}
CronExpression.Parse(string, CronFormat):顯式指定格式(Standard 或 Seconds)。GetNextOccurrence(DateTime, TimeZoneInfo = null):如果區間外返回 null。GetOccurrences(DateTime start, DateTime end, TimeZoneInfo = null):懶枚舉,按需生成。
使用示例
示例 1:每天下午 6 點執行
var expr = CronExpression.Parse("0 18 * * *"); // 標準 5 段
var next = expr.GetNextOccurrence(DateTime.Now);
Console.WriteLine($"下一次:{next}");
示例 2:工作日上午 9:30、下午 15:30 執行
var expr = CronExpression.Parse("30 9,15 * * MON-FRI");
foreach (var dt in expr.GetOccurrences(DateTime.Today, DateTime.Today.AddDays(1))) {
Console.WriteLine(dt);
}
示例 3:支持秒級,間隔 10 秒執行
var expr = CronExpression.Parse("*/10 * * * * *", CronFormat.IncludeSeconds);
await foreach (var dt in expr.GetOccurrencesAsync(DateTime.UtcNow, DateTime.UtcNow.AddMinutes(1))) {
Console.WriteLine(dt);
}
結合 PeriodicTimer
結合 PeriodicTimer 實現簡單調度器:
using System;
using System.Threading;
using System.Threading.Tasks;
using Cronos;
class Program
{
static async Task Main()
{
var expression = CronExpression.Parse("0 */5 * * * *", CronFormat.IncludeSeconds); // 每 5 秒
var timeZone = TimeZoneInfo.Utc;
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(1)); // 每秒檢查
using var cts = new CancellationTokenSource();
try
{
var next = expression.GetNextOccurrence(DateTimeOffset.UtcNow, timeZone);
while (await timer.WaitForNextTickAsync(cts.Token))
{
var now = DateTimeOffset.UtcNow;
if (next.HasValue && now >= next.Value)
{
Console.WriteLine($"Task executed at {now:HH:mm:ss}");
next = expression.GetNextOccurrence(now, timeZone);
}
}
}
catch (OperationCanceledException)
{
Console.WriteLine("Scheduler stopped");
}
}
}
結合 Quartz.NET
結合 Quartz.NET 實現完整調度:
using Cronos;
using Quartz;
using Quartz.Impl;
using System;
using System.Threading.Tasks;
public class MyJob : IJob
{
public Task Execute(IJobExecutionContext context)
{
Console.WriteLine($"Job executed at: {DateTimeOffset.Now:HH:mm:ss}");
return Task.CompletedTask;
}
}
class Program
{
static async Task Main()
{
var factory = new StdSchedulerFactory();
var scheduler = await factory.GetScheduler();
await scheduler.Start();
var job = JobBuilder.Create<MyJob>()
.WithIdentity("myJob", "group1")
.Build();
var cronExpression = CronExpression.Parse("0 0 8 * * ?");
var trigger = TriggerBuilder.Create()
.WithIdentity("myTrigger", "group1")
.WithCronSchedule(cronExpression.ToString())
.Build();
await scheduler.ScheduleJob(job, trigger);
Console.ReadLine(); // 保持運行
}
}
性能優化策略
表達式預編譯
// 靜態存儲預編譯表達式
private static readonly CronExpression _precompiledCron =
CronExpression.Parse("0 */15 * * * *", CronFormat.IncludeSeconds);
// 使用時直接調用
DateTime? next = _precompiledCron.GetNextOccurrence(DateTime.UtcNow);
批處理優化
// 批量計算未來100次執行
public List<DateTime> GetNextOccurrencesBatch(
CronExpression cron,
int count,
TimeZoneInfo tz)
{
var results = new List<DateTime>(count);
DateTime? current = DateTime.UtcNow;
for (int i = 0; i < count; i++)
{
current = cron.GetNextOccurrence(current.Value, tz);
if (!current.HasValue) break;
results.Add(current.Value);
}
return results;
}
常見使用場景
輕量級定時任務
- 在
Console、Worker Service中驅動週期性作業,無需完整Quartz。
動態調度配置
- 從數據庫或配置文件加載
Cron表達式,運行時動態調整執行計劃。
延遲/重試機制
- 配合消息隊列,實現複雜的重試策略(如間隔指數級、以下午/深夜時段執行等)。
報表/統計
- 定時生成日報、月報,可通過表達式控制「最後一個工作日」等複雜規則。
與其他工具的對比
-
NCrontab:- 廣泛用於
Azure Functions和其他.NET應用。 - 支持五字段和六字段表達式,但夏令時處理不如
Cronos直觀。 - 性能略低於
Cronos(解析約 100 ns vs 30 ns)。 - 社區支持更強,文檔豐富。
- 廣泛用於
-
Cronos:- 更現代的
API,支持DateTimeOffset和時區。 - 夏令時處理更符合
Vixie Cron。 - 性能更高,適合高頻任務。
- 更現代的
資源和文檔
NuGet包:https://www.nuget.org/packages/CronosGitHub倉庫:https://github.com/HangfireIO/CronosCron表達式測試工具:https://crontab.guru
總結
Cronos 是現代 .NET 應用中處理 cron 表達式的首選輕量級解決方案,尤其適合:
- 需要精確跨時區調度的全球性應用
- 運行在資源受限環境(如
AWS Lambda) - 高頻觸發的定時任務(>1次/秒)
- 與
Hangfire深度集成的調度系統