TickerQ: The Modern .NET Job Scheduler That Beats Quartz and Hangfire
Learn TickerQ, the fast, reflection-free .NET job scheduler with cron and time jobs, EF Core storage, retries, and a live dashboard, explained for beginners.
Think about the dabbawalas in Mumbai. Every day, thousands of lunch boxes leave homes, ride trains, and reach the right office desk at exactly the right time. Nobody stands and watches each box. There is a simple, trusted system. The box has a code. The system reads the code and delivers it on time, again and again, without mistakes.
Your .NET app needs the same kind of trusted helper. Some work should happen on a schedule, not when a user clicks a button. Sending a welcome email five minutes after sign-up. Cleaning old files every night. Making a sales report on the first day of every month. You do not want a tired person doing this by hand.
TickerQ is a modern background job scheduler for .NET. It runs your jobs at the times you choose, remembers them even after a restart, retries them if they fail, and shows you everything on a live dashboard. In this guide we will learn what it is, why people say it beats Quartz.NET and Hangfire, and how to set it up step by step. Short sentences. Small steps.
What problem does TickerQ solve?
Imagine a kitchen with a cook and a kitchen timer. The cook does not stare at the clock. The cook sets the timer and goes to do other work. When the time comes, the timer beeps, and the cook acts. The timer is dumb but reliable. The cook is smart but busy.
TickerQ is that reliable timer for your app. You describe when a job should run and what it should do. TickerQ watches the clock for you and fires the job at the right moment. You never write a messy loop full of Thread.Sleep.
Here is the same idea drawn as a simple flow of who does what.
Who does what in TickerQ
Steps
You
Set the schedule
TickerQ
Waits for the time
Job
Runs your code
Done
Result is saved
What makes TickerQ special?
TickerQ was built by Arcenox. It is fast, free, and modern. The big idea is that it does not use reflection to find your jobs. Instead it uses source generators. That means your jobs are wired up at compile time, before your app even runs. This makes it quick and friendly to Native AOT (ahead-of-time compiled apps).
Let us compare it with the two old favourites, Quartz.NET and Hangfire.
| Feature | TickerQ | Quartz.NET | Hangfire |
|---|---|---|---|
| Job discovery | Source generators (compile time) | Reflection | Reflection |
| Cron precision | Down to the second | Down to the second | Minute level |
| Built-in dashboard | Yes, real-time with SignalR | No (separate tools) | Yes |
| Storage | EF Core or Redis | ADO.NET or memory | SQL, Redis, and more |
| Native AOT friendly | Yes | Limited | Limited |
| Price | Free (MIT or Apache 2.0) | Free core, paid PRO | Free core, paid Pro |
You do not have to throw away Quartz or Hangfire. They are good tools. But TickerQ is a fresh, lightweight choice that fits very well in newer .NET apps.
The two kinds of jobs
TickerQ keeps things simple. There are only two kinds of jobs to learn.
- Time ticker - runs once at a future moment. Like "send this email in 5 minutes".
- Cron ticker - runs again and again on a pattern. Like "every night at midnight".
A cron pattern is a short text that means "run at these times". For example, 0 0 * * * means midnight every day. TickerQ even supports seconds, so you can be very precise.
Step 1: Install the packages
TickerQ ships as a few small NuGet packages. You pick the ones you need. Run these in your project folder.
dotnet add package TickerQ
dotnet add package TickerQ.EntityFrameworkCore
dotnet add package TickerQ.DashboardHere is what each one does:
| Package | What it gives you |
|---|---|
TickerQ | The core scheduler. The brain that watches time. |
TickerQ.EntityFrameworkCore | Saves jobs in your database so they survive restarts. |
TickerQ.Dashboard | A live web page to watch and control jobs. |
If you start very small, you can use just TickerQ and keep jobs in memory. But most real apps want the EF Core store so nothing is lost when the app stops.
Step 2: Write your first job
A job is just a normal method. You mark it with the [TickerFunction] attribute and give it a name. That name is how TickerQ finds it later.
using TickerQ.Utilities.Base;
public class EmailJobs
{
private readonly IEmailService _email;
public EmailJobs(IEmailService email) => _email = email;
// The name "SendWelcomeEmail" is how we will schedule this later.
[TickerFunction("SendWelcomeEmail")]
public async Task SendWelcomeEmail(
TickerFunctionContext context,
CancellationToken cancellationToken)
{
// Your real work goes here.
await _email.SendAsync("Welcome aboard!", cancellationToken);
}
}Two nice things to notice. First, the method gets a CancellationToken, so it can stop cleanly if the app shuts down. Second, the class uses normal dependency injection. You can inject any service you registered, just like in a controller.
Step 3: Register TickerQ in Program.cs
Now we tell our app to use TickerQ. This is where we turn on the database store and the dashboard.
using TickerQ.DependencyInjection;
using TickerQ.EntityFrameworkCore.DependencyInjection;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddTickerQ(options =>
{
// Run up to 4 jobs at the same time.
options.SetMaxConcurrency(4);
// Save jobs in our own EF Core database.
options.AddOperationalStore<AppDbContext>(efOpt =>
{
efOpt.UseModelCustomizerForMigrations();
efOpt.CancelMissedTickersOnApplicationRestart();
});
// Turn on the live dashboard at /tickerq-dashboard.
options.AddDashboard(basePath: "/tickerq-dashboard");
options.AddDashboardBasicAuth();
});
var app = builder.Build();
// Start the scheduler. This must run before app.Run().
app.UseTickerQ();
app.Run();A short word on the options:
SetMaxConcurrency(4)means at most 4 jobs run together. This protects your server from overload.AddOperationalStore<AppDbContext>plugs TickerQ into your existing EF CoreDbContext. Your jobs now live in the same database as your app data.CancelMissedTickersOnApplicationRestart()tidies up jobs that were missed while the app was down.AddDashboardgives you a web page.AddDashboardBasicAuthputs a simple login in front of it so strangers cannot poke at your jobs.
Step 4: Add the database tables
TickerQ stores jobs in a few tables. You create them with a normal EF Core migration. Run these two commands.
dotnet ef migrations add AddTickerQTables
dotnet ef database updateNow your database has tables for time tickers and cron tickers. When a job is scheduled, a row is written. When it runs, the row is updated. This is why your jobs are safe even if the power goes out.
Step 5: Schedule a one-time job
Let us schedule that welcome email to go out 5 minutes after a user signs up. We use the ITimeTickerManager. You inject it like any other service.
using TickerQ.Utilities.Entities;
using TickerQ.Utilities.Interfaces.Managers;
public class SignupService
{
private readonly ITimeTickerManager<TimeTickerEntity> _timeTicker;
public SignupService(ITimeTickerManager<TimeTickerEntity> timeTicker)
=> _timeTicker = timeTicker;
public async Task OnUserSignedUp(int userId)
{
await _timeTicker.AddAsync(new TimeTickerEntity
{
Function = "SendWelcomeEmail", // matches our [TickerFunction]
ExecutionTime = DateTime.UtcNow.AddMinutes(5),
Request = TickerHelper.CreateTickerRequest(userId),
Retries = 3, // try 3 more times if it fails
RetryIntervals = new[] { 60, 300, 900 } // wait 1m, 5m, 15m
});
}
}See how the Function value is "SendWelcomeEmail". That string matches the name in our [TickerFunction("SendWelcomeEmail")] attribute. That is the link between "when to run" and "what to run".
The Request lets you pass data, like the userId. Your job can read it back when it runs. The retry settings mean that if the email server is down, TickerQ will not give up at once. It waits and tries again.
Step 6: Schedule a repeating job
Now a job that runs every night. We can do it in two ways.
The easy way is to put the cron pattern right on the attribute. TickerQ reads it at compile time and registers the schedule for you.
public class CleanupJobs
{
// Runs every 6 hours. No extra code needed to schedule it.
[TickerFunction("CleanupTempFiles", "0 */6 * * *")]
public Task CleanupTempFiles(
TickerFunctionContext context,
CancellationToken cancellationToken)
{
Console.WriteLine("Cleaning temporary files...");
return Task.CompletedTask;
}
}The flexible way is to add the cron job at runtime with the ICronTickerManager. Use this when the schedule comes from a database or a user setting, not from code.
await _cronTicker.AddAsync(new CronTickerEntity
{
Function = "GenerateMonthlyReport",
CronExpression = "0 0 1 * *", // midnight on the 1st of each month
Retries = 2,
RetryIntervals = new[] { 60, 300 }
});Here is a quick guide to reading a cron pattern. The five parts are minute, hour, day of month, month, and day of week.
| Cron pattern | Plain English |
|---|---|
*/5 * * * * | Every 5 minutes |
0 * * * * | Every hour, on the hour |
0 0 * * * | Every day at midnight |
0 9 * * 1 | Every Monday at 9 AM |
0 0 1 * * | Midnight on the 1st of the month |
How the scheduler actually works
You do not need to know the inner machine to use TickerQ. But a simple picture helps you trust it. TickerQ keeps checking the database for jobs whose time has come. When it finds one, it claims it so no other server grabs the same job. Then it runs your method.
The scheduler loop
Steps
Check DB
Find due jobs
Claim job
Lock so it runs once
Run job
Call your method
Record result
Save success or retry
This claim step is important when you run many copies of your app. Without it, every copy might run the same midnight job, and your users would get five report emails instead of one. TickerQ's locking (in EF Core mode) makes sure each job runs only once across all your servers.
The real-time dashboard
This is one of the best parts of TickerQ. The TickerQ.Dashboard package gives you a web page where you can see every job live. It uses SignalR, so the screen updates by itself without you refreshing.
On the dashboard you can:
- See which jobs are pending, running, done, or failed.
- Trigger a job by hand for testing.
- Cancel a job that you no longer want.
- Watch retries happen in real time.
You open it at the path you set, for example /tickerq-dashboard. Because we added basic auth, it asks for a username and password first. Always protect this page in production. It is powerful, and you do not want random visitors triggering your jobs.
A common real-life example
Let us tie it all together with a story you can picture. A small online shop wants three scheduled jobs:
- Send a welcome email 5 minutes after sign-up (one-time).
- Retry failed payments every 30 minutes (repeating).
- Email a sales report at 8 AM every Monday (repeating).
With TickerQ, the welcome email is a time ticker. The other two are cron tickers. Each one is a small method with a [TickerFunction] name. The shop owner watches all three on the dashboard and sleeps well, because failed jobs retry on their own.
TickerQ vs a plain BackgroundService
You might ask, "Why not just use a BackgroundService and a while loop?" That works for the simplest cases. But it gets hard fast. Here is the difference.
| You want this | Plain BackgroundService | TickerQ |
|---|---|---|
| Run a loop while app is alive | Easy | Easy |
| Run "every Monday 9 AM" | Hard to get right | One cron line |
| Survive an app restart | No, the plan is lost | Yes, stored in the database |
| Run once across many servers | No, it runs everywhere | Yes, with locking |
| Retry on failure | You build it | Built in |
| See jobs on a screen | No | Yes, live dashboard |
A BackgroundService is a hammer. TickerQ is a full toolbox. For real scheduling needs, the toolbox saves you days of work.
A few good habits
A short list of tips so your jobs stay healthy.
- Use UTC times. Store and schedule in
DateTime.UtcNow. Time zones cause silent bugs. Convert to local time only when you show it to a person. - Keep jobs short and safe to retry. A job might run twice if something odd happens. Design it so running twice does no harm. This is called being "idempotent".
- Always honour the
CancellationToken. Pass it down into your database and HTTP calls so the job can stop fast on shutdown. - Protect the dashboard. Use basic auth or your own login in production.
- Set a sensible concurrency. Too high and you flood your server. Too low and jobs pile up. Start with a small number like 4 and watch.
A note on licensing in the .NET world
The .NET ecosystem has changed lately. Some popular libraries moved to commercial licenses. For example, MediatR and MassTransit are now commercially licensed for many uses. That is fine, but it means you should always check the license before you build on a tool.
TickerQ is friendly here. It is dual licensed under MIT or Apache 2.0, both of which are free and open source. You can use it in commercial products without paying. The dashboard is included for free too. This makes it an easy "yes" for many teams.
Quick recap
- TickerQ is a modern, fast, free background job scheduler for .NET, built by Arcenox.
- It uses source generators, not reflection, so jobs are wired up at compile time and it works with Native AOT.
- There are two job types: a time ticker runs once, and a cron ticker repeats on a pattern. Cron precision goes down to the second.
- You install
TickerQ, addTickerQ.EntityFrameworkCorefor safe storage, andTickerQ.Dashboardfor a live view. - Mark a method with
[TickerFunction("Name")], register withAddTickerQ, then callapp.UseTickerQ(). - Schedule one-time jobs with
ITimeTickerManagerand repeating jobs withICronTickerManageror a cron on the attribute. - It supports retries, distributed locking so a job runs once across many servers, and a real-time dashboard.
- It is MIT or Apache 2.0 licensed, so it is free for commercial use, unlike some libraries that recently moved to paid licenses.
References and further reading
- TickerQ Official Website and Docs
- TickerQ GitHub Repository (Arcenox-co)
- TickerQ on NuGet
- From Zero to Scheduled: TickerQ for .NET (DEV Community)
- .NET Job Scheduling with TickerQ (Daily DevOps & .NET)
Related Posts
Scheduling Background Jobs with Quartz.NET in ASP.NET Core
Learn Quartz.NET step by step in ASP.NET Core: jobs, triggers, cron schedules, dependency injection, and database persistence, explained for beginners.
Running Background Tasks in ASP.NET Core: A Beginner's Guide
Learn background tasks in ASP.NET Core with simple examples: IHostedService, BackgroundService, timers, scoped services, and a queue using Channels, with clear diagrams.
Adding Real-Time Functionality to .NET Apps with SignalR
Learn ASP.NET Core SignalR step by step: hubs, clients, groups, and scaling with Redis or Azure, explained for absolute beginners.
Improving ASP.NET Core Dependency Injection with Scrutor
Learn how Scrutor makes ASP.NET Core dependency injection easier with assembly scanning and decoration, explained in simple, beginner-friendly steps.
Master Configuration in ASP.NET Core with the Options Pattern
Learn the ASP.NET Core options pattern step by step: bind appsettings, use IOptions, IOptionsSnapshot, IOptionsMonitor, and validate config safely.
How to Add JWT Authentication to SignalR Hubs in ASP.NET Core
A beginner-friendly guide to securing SignalR hubs with JWT tokens in ASP.NET Core, including the access_token query string trick and the [Authorize] attribute.