🔧 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
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();
🎯 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;
}
▶️ 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 }
});
🔁 CronTicker example (recurring)
await cronTickerManager.AddAsync(new CronTicker
{
Function = "CleanupLogs",
CronExpression = "0 */6 * * *",
Retries = 2,
RetryIntervals = new[] { 60, 300 }
});
🗔️ 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:
Using
UseModelCustomizerForMigrations()
: Best for projects that want to avoid cluttering theirDbContext
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.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());
}
Apply migrations:
dotnet ef migrations add AddTickerQTables
dotnet ef database update
🌐 Dashboard UI & Real‑Time Monitoring
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();
Add credentials in appsettings.json
:
"TickerQBasicAuth": {
"Username": "admin",
"Password": "admin"
}
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();
});
});
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!
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.