Quartz.NET vs. Hangfire: der richtige Scheduler für .NET-Apps
Quartz.NET und Hangfire lösen überlappende, aber unterschiedliche Probleme bei der .NET-Hintergrundverarbeitung. Hier der tatsächliche Trade-off — und eine dritte Option für den Teil, der nur „das hier nach Zeitplan ausführen" ist.
Jedes .NET-Team, das geplante oder Hintergrundarbeit ausführt, stellt sich irgendwann dieselbe Frage: Quartz.NET oder Hangfire? Die ehrliche Antwort ist, dass sie sich weniger überlappen, als Vergleichsartikel suggerieren — sie sind für leicht unterschiedliche Aufgaben gebaut, und eine falsche Wahl zeigt sich meist erst später als Reibung, nicht als Neuschreiben.
Quartz.NET: in erster Linie ein Scheduler
Quartz.NET ist ein Port der Java-Quartz-Bibliothek — eine Scheduling-Engine.
Aufgebaut auf ITrigger und IJob, Cron-Ausdrücken, Kalendern (Feiertage
auslassen, nur Geschäftszeiten-Fenster) und Cluster-Unterstützung, um denselben
Scheduler über mehrere Knoten zu betreiben, ohne doppelt zu feuern. Es liefert
keine Job-Queue, kein Dashboard und kein Modell für „aus einem Web-Request
enqueuen und später verarbeiten” — dafür ist es nicht gedacht. Sie definieren
Jobs und Trigger in Code oder Konfiguration, und Quartz feuert sie.
Wenn Ihr Problem wirklich „präzises, kalenderbewusstes, geclustertes Scheduling für In-Process-Arbeit” ist, ist Quartz.NET genau dafür gebaut — auf eine Art, die im .NET-Ökosystem sonst nichts ganz erreicht.
Hangfire: Hintergrundverarbeitung mit Persistenz
Hangfire startet aus einem anderen Blickwinkel: zuverlässige
Hintergrund-Job-Verarbeitung. BackgroundJob.Enqueue(() => SendEmail(id))
aus einer Controller-Action, bei Fehler automatisch wiederholt, im Dashboard
sichtbar, persistiert in SQL Server oder Redis, damit Jobs einen App-Neustart
überleben. Es unterstützt auch wiederkehrende Jobs
(RecurringJob.AddOrUpdate), wo die Überlappung mit Quartz beginnt — aber das
speicherbasierte, dashboard-zentrierte Design ist das eigentliche
Unterscheidungsmerkmal, nicht die Scheduling-Syntax.
Wenn Ihr Problem „durch Anwendungsereignisse ausgelöste Hintergrundarbeit mit Wiederholungen und Sichtbarkeit” ist, ist Hangfire die direktere Wahl — Persistenz und Dashboard sind bereits eingebaut.
Wo sie sich tatsächlich überlappen
Die Überlappung ist eng: wiederkehrende, zeitplangetriebene Jobs ohne
eingehenden Trigger — ein nächtlicher Report, eine Aufräumaufgabe, eine
Digest-E-Mail. Quartz.NET kann das mit einem CronTrigger. Hangfire kann das
mit RecurringJob.AddOrUpdate. Beides funktioniert. Keines ist falsch. Den
Ausschlag gibt meist, was Ihre App bereits für ihre anderen Job-Typen
verdrahtet hat — eine zweite Scheduling-Bibliothek nur für die wiederkehrenden
Jobs hinzuzufügen, lohnt sich selten.
Die Option, die keiner der Vergleichsartikel erwähnt
Beide Bibliotheken laufen innerhalb Ihres Anwendungsprozesses, was bedeutet, dass CPU, Speicher und Datenbankverbindungen eines wiederkehrenden Jobs aus demselben Pool kommen, der auch Requests bedient. Ein Report-Job, der Zeilen 90 Sekunden lang sperrt, läuft im selben Prozess — und oft gegen dieselbe Datenbank —, die gleichzeitig HTTP-Requests beantworten soll.
Für den Teilbereich von Jobs, die rein zeitplangetrieben sind und nicht Quartz’ Clustering-Kalender oder Hangfires Queue-/Continuation-Modell brauchen, können Sie beide auslassen und den Trigger komplett externalisieren: einen HTTP-Endpunkt bereitstellen, und etwas außerhalb Ihres Prozesses ruft ihn planmäßig auf — mit eigenem Retry/Backoff und Ausführungsprotokoll. Das ist die ganze Idee hinter SteadyCron — siehe die ausführlichere Aufschlüsselung in SteadyCron vs. Hangfire dafür, wo dieser Trade-off tatsächlich landet, auch für Jobs, die Sie weiterhin Quartz.NET oder Hangfire überlassen möchten.
Eines auswählen
- Quartz.NET, wenn Sie kalenderbewusstes oder geclustertes Scheduling für In-Process-Jobs brauchen und keine Job-Queue benötigen.
- Hangfire, wenn Sie eine Job-Queue mit Persistenz und Dashboard brauchen und wiederkehrende Jobs ein Zusatzfeature sind, das Sie darauf aufsetzen.
- Keines, für den reinen Zeitplan-Teilbereich, wenn der Job überhaupt nicht innerhalb Ihrer App laufen muss — den Trigger nach außen zu verschieben, entfernt die Scheduling-Bibliothek und ihren Speicher vollständig aus der Gleichung.