ChronoScheduler 1.0.0

dotnet add package ChronoScheduler --version 1.0.0
                    
NuGet\Install-Package ChronoScheduler -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ChronoScheduler" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ChronoScheduler" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="ChronoScheduler" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ChronoScheduler --version 1.0.0
                    
#r "nuget: ChronoScheduler, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ChronoScheduler@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ChronoScheduler&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=ChronoScheduler&version=1.0.0
                    
Install as a Cake Tool

ChronoScheduler

A lightweight, zero-dependency task scheduler for .NET with a fluent API, async execution, mutex groups, and pluggable persistence.

NuGet

Features

  • Fluent scheduling API — readable, chainable job configuration
  • Async-first — all jobs are async Task, with full CancellationToken support
  • 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 ITrigger for any scheduling logic
  • Error handling — plug in your own IJobErrorHandler for 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

MIT

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .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