Blog

TickerQ vs Hangfire: the .NET scheduler comparison, honestly

TickerQ vs Hangfire for .NET background jobs — source generators vs reflection, EF Core storage, no polling, dashboards, and maturity risk.

SteadyCron dotnettickerqhangfirequartzscheduling

TickerQ is the first .NET job scheduler in years that’s made teams seriously reconsider Hangfire. It’s not a clone with a nicer dashboard — the architecture is different in ways that matter. It’s also considerably younger, which matters just as much.

The short version

Hangfire is a background job system that also does recurring schedules. TickerQ is a scheduler that also persists jobs. If you enqueue work from request handlers, that difference decides it before you compare a single feature.

Where they actually differ

TickerQHangfire
Job discoverySource generators, compile-timeReflection, runtime
PersistenceEF Core (your DbContext)SQL Server, PostgreSQL, Redis, …
How due work is foundIn-memory timers, no pollingPolls storage on an interval
DashboardYes, liveYes, mature
Fire-and-forget from a requestLimitedCore strength
Continuations / batches / chainsNoYes (batches are Hangfire Pro)
Cron supportYesYes (recurring jobs)
MaturityYoungBattle-tested, years of production
LicenceApache 2.0 or MIT (your choice)LGPL 3.0 core, paid Pro

Source generators vs reflection

TickerQ resolves jobs at compile time. Practically: a mistyped job reference is a build error rather than a runtime one, startup does less work, and it behaves predictably under trimming and AOT — which is where Hangfire’s reflection-based discovery gets awkward.

The cost is that everything must be knowable at compile time. Dynamically constructed job types don’t fit the model.

Polling vs in-memory timers

This is the difference people feel most. Hangfire asks storage “is anything due?” on an interval, forever, whether or not anything is scheduled. With a handful of nightly jobs you’re running queries every few seconds to learn that nothing has changed.

TickerQ schedules against in-memory timers and touches the database when work actually changes. Materially less idle load.

The subtlety: in-memory timers are per-process state. Multi-instance coordination becomes TickerQ’s problem to solve rather than a natural consequence of everyone polling the same table, and it’s a younger implementation of a genuinely hard problem. Hangfire’s approach is less efficient and more obviously correct.

EF Core persistence

If you’re already an EF Core shop, TickerQ’s tables live in your DbContext and travel through your normal migration pipeline. That’s a real ergonomic win over Hangfire’s separate schema and its own initialization path.

The flip side is coupling: your scheduler’s schema is now in your application’s migration history. If you weren’t using EF Core, this is a reason not to start.

TickerQ vs Quartz.NET

Different axis. Quartz.NET beats both on scheduling semantics — calendars, misfire policies, trigger priorities, mature clustering. If you need “every business day except holidays, and if we missed Tuesday, don’t run it four times on Wednesday,” Quartz is the only one of the three with a real answer.

TickerQ is easier to live with day to day and has a dashboard. Quartz is the one you pick when the scheduling rules themselves are the hard part.

Which should you pick?

TickerQ if you’re greenfield, already on EF Core, want a dashboard without Hangfire’s storage churn, and can tolerate a young dependency. It’s dual-licensed under Apache 2.0 and MIT — take your pick — which settles some legal reviews that LGPL doesn’t.

Hangfire if you enqueue from request handlers, need continuations or batches, or if “boring and proven” is worth more to you than the polling savings. For most production systems it still is.

Neither, if what you actually have is a handful of recurring jobs — see below.

The honest maturity caveat

TickerQ is genuinely good and genuinely young. Hangfire has had a decade to discover its edge cases in other people’s production systems; TickerQ is still finding some of its own. That’s not a reason to avoid it — it’s a reason to be deliberate about where you adopt it first. A new service is a fine place. The system that runs payroll is not.

What neither one fixes

Both run inside your application process, so both inherit the same failure: if the app is down, deploying, recycling, or scaled to zero, nothing is scheduled — and nothing tells you. A scheduler cannot alert you about its own death. A job that quietly stopped looks identical to a job with nothing to do, right up until someone notices the reports stopped three weeks ago.

Two ways to close that gap, and they compose:

  1. Keep the library, add an outside witness. The job pings a monitor when it completes; a missing ping raises the alert. Our .NET SDK does it in one attribute, and heartbeat monitoring is free down to once-a-minute schedules.
  2. Move the trigger out of the process. If the job is really “call this endpoint on a schedule”, it doesn’t need to be a library at all — see Hangfire alternatives for that trade-off in full, or the four-way library comparison if you’re still choosing.