From Zero to Scheduled: TickerQ for .NET
Mark Jack

Mark Jack @markjackmilian

About: Microsoft MCSD, Certified Xamarin Developer & Azure Solution Architect Expert. Building enterprise-grade solutions with cloud-native architecture.

Location:
Italy
Joined:
Nov 26, 2021

From Zero to Scheduled: TickerQ for .NET

Publish Date: Jul 4
2 1

🔧 Introducing TickerQ – A Modern .NET Scheduler

As a .NET dev who’s tried just about every job scheduler out there, I wanted to share why TickerQ has quickly become my go-to. It’s fast, clean, and built with devs in mind. Here’s a quick dive into what it offers and how to get started.

TickerQ is a high-performance, reflection‑free background task scheduler for .NET. It leverages source generators, offers cron and time‑based execution, supports EF Core persistence, and includes a real-time dashboard UI—all with minimal overhead.

Key features include:

  • Source‑generator discovery (no reflection)
  • TimeTickers (one-time) and CronTickers (recurring)
  • EF Core integration for persistence of jobs, states, and history
  • Dashboard UI built with SignalR and Tailwind (Vue.js-based)
  • Retry policies, throttling, cooldowns
  • Distributed coordination, priorities, DI support

🚀 Installing & Configuring

Use the following NuGet packages. While only the core TickerQ package is required, in this example we will also set up optional packages for EF Core persistence and the real-time dashboard:

dotnet add package TickerQ
dotnet add package TickerQ.EntityFrameworkCore
dotnet add package TickerQ.Dashboard
Enter fullscreen mode Exit fullscreen mode

In Program.cs

builder.Services.AddTickerQ(options =>
{
    options.SetMaxConcurrency(4);
    options.SetExceptionHandler<MyExceptionHandler>();
    options.AddOperationalStore<MyDbContext>(efOpt =>
    {
        efOpt.UseModelCustomizerForMigrations(); // Design-time migrations only
        efOpt.CancelMissedTickersOnApplicationRestart();
    });
    options.AddDashboard(basePath: "/tickerq-dashboard");
    options.AddDashboardBasicAuth();
});

app.UseTickerQ();
Enter fullscreen mode Exit fullscreen mode

🎯 Defining and Scheduling Jobs

TickerQ uses a [TickerFunction] attribute and compile‑time source generation. You can define multiple scheduled functions with different triggers:

[TickerFunction("CleanupLogs")]
public static async Task CleanupLogs(TickerRequest req, CancellationToken ct)
{
    // Do cleanup work...
}

[TickerFunction("CleanupTempFiles","0 */6 * * *")]
public static Task CleanupTempFiles(TickerRequest req, CancellationToken ct)
{
    Console.WriteLine("Cleaning temporary files...");
    return Task.CompletedTask;
}
Enter fullscreen mode Exit fullscreen mode

▶️ TimeTicker example (one‑off or delayed)

await timeTickerManager.AddAsync(new TimeTicker
{
  Function = "SendWelcomeEmail",
  Request = new { UserId = 123 },
  RunAt = DateTime.UtcNow.AddMinutes(5),
  Retries = 3,
  RetryIntervals = new[] { 60, 120, 300 }
});
Enter fullscreen mode Exit fullscreen mode

🔁 CronTicker example (recurring)

await cronTickerManager.AddAsync(new CronTicker
{
  Function = "CleanupLogs",
  CronExpression = "0 */6 * * *",
  Retries = 2,
  RetryIntervals = new[] { 60, 300 }
});
Enter fullscreen mode Exit fullscreen mode

🗔️ Persistence with EF Core

To enable persistence, you'll need to create the necessary tables using EF Core migrations.

TickerQ’s EF Core provider persists tickers, execution history, and job states into your DbContext.

There are two integration methods:

  1. Using UseModelCustomizerForMigrations(): Best for projects that want to avoid cluttering their DbContext class with extra config. This approach automatically wires in the schema only during EF Core design-time operations (e.g., dotnet ef migrations add). At runtime, it won't interfere with your model.

  2. Manual configuration: Required if you want full control or need schema visibility in your runtime model. This requires directly applying the necessary configurations in your OnModelCreating method.

protected override void OnModelCreating(ModelBuilder builder)
{
    base.OnModelCreating(builder);
    builder.ApplyConfiguration(new TimeTickerConfigurations());
    builder.ApplyConfiguration(new CronTickerConfigurations());
    builder.ApplyConfiguration(new CronTickerOccurrenceConfigurations());
}
Enter fullscreen mode Exit fullscreen mode

Apply migrations:

dotnet ef migrations add AddTickerQTables
dotnet ef database update
Enter fullscreen mode Exit fullscreen mode

🌐 Dashboard UI & Real‑Time Monitoring

Image description

The TickerQ Dashboard is a modern Vue.js-based web UI powered by SignalR and Tailwind CSS. It provides live updates and comprehensive control over job scheduling and monitoring.

Setup

builder.Services.AddTickerQ(opt =>
{
    opt.AddOperationalStore<MyDbContext>();
    opt.SetInstanceIdentifier("MyAppTickerQ");
    opt.AddDashboard(basePath: "/tickerq-dashboard");
    opt.AddDashboardBasicAuth();
});

app.UseTickerQDashboard();
Enter fullscreen mode Exit fullscreen mode

Add credentials in appsettings.json:

"TickerQBasicAuth": {
  "Username": "admin",
  "Password": "admin"
}
Enter fullscreen mode Exit fullscreen mode

Features

  • System Overview: throughput, active nodes, concurrency.
  • Job Status Breakdown: Done, DueDone, Failed, Pending.
  • CronTickers View: timeline, manual trigger, duplication, deletion.
  • TimeTickers View: execution history, live retry/cancel/edit/delete options.
  • Add/Edit Job: intuitive UI to define function, payload, retry policy.

🔐 Distributed Locking via EF Core

To safely run TickerQ across multiple nodes—without overlapping job executions—TickerQ uses EF Core-based distributed locking.

How It Works

  • A node picks up a job and marks itself as OwnerNode in the database.
  • Row-level locking (e.g., SELECT ... FOR UPDATE) ensures mutual exclusion.
  • Failed jobs release locks automatically, enabling retry by others.
  • Cooldowns and expiry provide safety against zombie tasks.
builder.Services.AddTickerQ(options =>
{
  options.AddOperationalStore<MyDbContext>(efOpt =>
  {
    efOpt.CancelMissedTickersOnApplicationRestart();
  });
});
Enter fullscreen mode Exit fullscreen mode

What You Get

Feature Description
Single ownership Only one node can process a job at a time
Failover safety Locks are released on restart or timeout
Coordination simplicity No external lock store needed
EF Core-friendly Leverages your existing DB and transaction logic

Use this if you scale horizontally and want safe, DB-driven coordination. For very high throughput or cross-cluster workloads, consider Redis or Zookeeper.


⚖️ How TickerQ Stacks Up

Compared to classic schedulers like Hangfire or Quartz.NET:

Feature TickerQ Hangfire Quartz.NET
Reflection-free ✅ source gen ❌ reflection ❌ reflection
Time + cron jobs ✅ both ✅ cron, limited delayed ✅ both
EF Core persistence ✅ built-in
Dashboard UI ✅ real-time first-party ⚠️ basic ⚠️ third-party UI req'd
Retries & cooldowns ✅ advanced ⚠️ basic ⚠️ manual config req'd
Distributed locking ✅ via EF Core ⚠️ partial
DI support ✅ native ⚠️ type-based limited ⚠️ manual config
Performance ✅ low overhead ⚠️ storage-bound ⚠️ thread-blocking

✅ When to Choose TickerQ

Choose TickerQ if you:

  • Prefer compile-time safety over runtime reflection
  • Need a lightweight, fast scheduler
  • Want cron and time jobs with retries/cooldowns
  • Use EF Core or need distributed coordination
  • Value a first-party, real-time dashboard

Limitations to Consider

  • No Redis or distributed cache persistence (yet)
  • No node-specific routing or tag-based job targeting
  • Throttling and job chaining features are in progress

🔺 Conclusion

TickerQ offers a modern, efficient, and developer-friendly background scheduler featuring:

  • Reflection-free architecture via source generation
  • Cron and time-based scheduling
  • Robust EF Core persistence
  • Real-time dashboard with live updates

It's especially suitable for applications that care about performance, visibility, and maintainability. For deeper dives, visit the official docs and check out the benchmarks on Medium.

If you found this article helpful, follow me on GitHub, Twitter and Bluesky.

Thanks for reading!

Comments 1 total

  • Damiaan
    DamiaanAug 18, 2025

    I am a little disappointed in the article. With all respect to your work, but this is exactly the same as the documentation without any added value.

Add comment