ChronoScheduler 1.0.0
dotnet add package ChronoScheduler --version 1.0.0
NuGet\Install-Package ChronoScheduler -Version 1.0.0
<PackageReference Include="ChronoScheduler" Version="1.0.0" />
<PackageVersion Include="ChronoScheduler" Version="1.0.0" />
<PackageReference Include="ChronoScheduler" />
paket add ChronoScheduler --version 1.0.0
#r "nuget: ChronoScheduler, 1.0.0"
#:package ChronoScheduler@1.0.0
#addin nuget:?package=ChronoScheduler&version=1.0.0
#tool nuget:?package=ChronoScheduler&version=1.0.0
ChronoScheduler
A lightweight, zero-dependency task scheduler for .NET with a fluent API, async execution, mutex groups, and pluggable persistence.
Features
- Fluent scheduling API — readable, chainable job configuration
- Async-first — all jobs are
async Task, with fullCancellationTokensupport - Parallel execution — jobs run concurrently by default
- Mutex groups — jobs in the same group are serialised, preventing conflicts
- Pluggable persistence — swap in Redis or EF Core to survive restarts
- Custom triggers — implement
ITriggerfor any scheduling logic - Error handling — plug in your own
IJobErrorHandlerfor logging, retries, or alerts - Zero dependencies — the core package has no third-party dependencies
- Companion packages — Cron expressions, Redis, EF Core, and ASP.NET Core hosting
Packages
| Package | Description | Dependencies |
|---|---|---|
| ChronoScheduler | Core library | None |
| ChronoScheduler.Cron | Cron expression triggers | Cronos |
| ChronoScheduler.Store.Redis | Redis state persistence | StackExchange.Redis |
| ChronoScheduler.Store.EfCore | Database state persistence | EF Core |
| ChronoScheduler.Hosting | ASP.NET Core / Generic Host integration | Microsoft.Extensions.Hosting |
Quick Start
1. Define a job
using ChronoScheduler;
public class CleanupJob : IJob
{
public async Task ExecuteAsync(CancellationToken cancellationToken)
{
// Your job logic here
await Database.CleanupOldRecordsAsync(cancellationToken);
}
}
2. Schedule it
var scheduler = new Scheduler();
scheduler.Schedule("cleanup", new CleanupJob())
.Every(TimeSpan.FromHours(1))
.Build();
await scheduler.StartAsync();
3. Stop it
await scheduler.StopAsync();
Scheduling Options
Recurring interval
scheduler.Schedule("heartbeat", new HeartbeatJob())
.Every(TimeSpan.FromSeconds(30))
.Build();
Daily time window
Run once per day within a UTC time window:
scheduler.Schedule("nightly-backup", new BackupJob())
.DailyBetween(new TimeSpan(2, 0, 0), new TimeSpan(4, 0, 0))
.Build();
// Or with TimeOnly (net6.0+):
scheduler.Schedule("nightly-backup", new BackupJob())
.DailyBetween(new TimeOnly(2, 0), new TimeOnly(4, 0))
.Build();
Cron expressions (companion package)
using ChronoScheduler.Cron;
scheduler.Schedule("every-five-min", new PollJob())
.WithCron("*/5 * * * *")
.Build();
Custom triggers
public class WeekdaysOnlyTrigger : ITrigger
{
private readonly TimeSpan _interval;
public WeekdaysOnlyTrigger(TimeSpan interval) => _interval = interval;
public bool IsDue(DateTimeOffset utcNow, DateTimeOffset? lastRunUtc)
{
if (utcNow.DayOfWeek == DayOfWeek.Saturday || utcNow.DayOfWeek == DayOfWeek.Sunday)
return false;
if (lastRunUtc == null) return true;
return utcNow - lastRunUtc.Value >= _interval;
}
}
scheduler.Schedule("weekday-report", new ReportJob())
.WithTrigger(new WeekdaysOnlyTrigger(TimeSpan.FromHours(1)))
.Build();
Mutex Groups
By default, all jobs run in parallel. Use mutex groups to prevent specific jobs from overlapping:
// These two will never run at the same time:
scheduler.Schedule("db-update", new UpdateJob())
.Every(TimeSpan.FromMinutes(10))
.InMutexGroup("database")
.Build();
scheduler.Schedule("db-backup", new BackupJob())
.DailyBetween(new TimeSpan(3, 0, 0), new TimeSpan(5, 0, 0))
.InMutexGroup("database")
.Build();
// This job runs independently, even if the above are busy:
scheduler.Schedule("send-emails", new EmailJob())
.Every(TimeSpan.FromMinutes(5))
.Build();
Error Handling
By default, exceptions are swallowed and the job's last-run time is still recorded (to prevent a broken job from firing every tick). Plug in your own handler:
public class LoggingErrorHandler : IJobErrorHandler
{
public Task OnErrorAsync(string jobId, Exception exception)
{
Console.Error.WriteLine($"[{jobId}] Failed: {exception.Message}");
return Task.CompletedTask;
}
}
var scheduler = new Scheduler(errorHandler: new LoggingErrorHandler());
Persistence
By default, job state lives in memory. To survive process restarts, use a persistent store:
Redis
using ChronoScheduler.Store.Redis;
using StackExchange.Redis;
var redis = ConnectionMultiplexer.Connect("localhost");
var store = new RedisJobStateStore(redis);
var scheduler = new Scheduler(store: store);
Entity Framework Core
using ChronoScheduler.Store.EfCore;
var options = new DbContextOptionsBuilder<ChronoSchedulerDbContext>()
.UseSqlServer(connectionString)
.Options;
var db = new ChronoSchedulerDbContext(options);
await db.Database.EnsureCreatedAsync();
var store = new EfCoreJobStateStore(db);
var scheduler = new Scheduler(store: store);
ASP.NET Core / Generic Host Integration
using ChronoScheduler.Hosting;
builder.Services.AddChronoScheduler(scheduler =>
{
scheduler.Schedule("cleanup", new CleanupJob())
.Every(TimeSpan.FromHours(1))
.Build();
scheduler.Schedule("report", new ReportJob())
.DailyBetween(new TimeSpan(8, 0, 0), new TimeSpan(9, 0, 0))
.Build();
});
Or with full DI control:
builder.Services.AddChronoScheduler(sp =>
{
var store = sp.GetRequiredService<IJobStateStore>();
var errorHandler = sp.GetRequiredService<IJobErrorHandler>();
var scheduler = new Scheduler(store: store, errorHandler: errorHandler);
scheduler.Schedule("my-job", sp.GetRequiredService<MyJob>())
.Every(TimeSpan.FromMinutes(5))
.Build();
return scheduler;
});
Testing
The scheduler is fully testable. Use ISystemClock to control time and RunStepAsync() to manually tick:
public class MockClock : ISystemClock
{
private DateTimeOffset _now;
public MockClock(DateTimeOffset start) => _now = start;
public DateTimeOffset UtcNow => _now;
public void Advance(TimeSpan duration) => _now = _now.Add(duration);
}
var clock = new MockClock(DateTimeOffset.UtcNow);
var scheduler = new Scheduler(clock: clock);
scheduler.Schedule("test-job", myJob)
.Every(TimeSpan.FromMinutes(5))
.Build();
await scheduler.RunStepAsync(); // First run (immediate)
clock.Advance(TimeSpan.FromMinutes(5));
await scheduler.RunStepAsync(); // Second run
API Reference
Core Types
| Type | Description |
|---|---|
IJob |
Implement to define job logic (ExecuteAsync) |
Scheduler |
The main scheduler — schedules, starts, stops |
JobBuilder |
Fluent builder returned by Scheduler.Schedule() |
JobDefinition |
Describes a registered job (id, trigger, mutex group) |
ISystemClock |
Abstraction over system time for testability |
SystemClock |
Default real-time clock |
Triggers
| Type | Description |
|---|---|
ITrigger |
Implement to define custom scheduling logic |
IntervalTrigger |
Fires at a fixed recurring interval |
DailyWindowTrigger |
Fires once per day within a time window |
CronTrigger |
Fires on a cron schedule (companion package) |
Persistence
| Type | Description |
|---|---|
IJobStateStore |
Implement to persist job state |
InMemoryJobStateStore |
Default in-memory store (no persistence) |
RedisJobStateStore |
Redis-backed store (companion package) |
EfCoreJobStateStore |
EF Core-backed store (companion package) |
Error Handling
| Type | Description |
|---|---|
IJobErrorHandler |
Implement to handle job failures |
DefaultJobErrorHandler |
Swallows exceptions silently |
License
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- No dependencies.
-
net10.0
- No dependencies.
NuGet packages (4)
Showing the top 4 NuGet packages that depend on ChronoScheduler:
| Package | Downloads |
|---|---|
|
ChronoScheduler.Hosting
Microsoft.Extensions.Hosting integration for ChronoScheduler. Adds services.AddChronoScheduler() for ASP.NET Core and Generic Host apps. |
|
|
ChronoScheduler.Store.EfCore
Entity Framework Core job state store for ChronoScheduler. Persists last-run times to any EF Core-supported database. |
|
|
ChronoScheduler.Store.Redis
Redis-backed job state store for ChronoScheduler. Persists last-run times so scheduled jobs survive restarts. |
|
|
ChronoScheduler.Cron
Cron expression trigger for ChronoScheduler. Adds .WithCron("0 */2 * * *") to the fluent API. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 207 | 2/20/2026 |