Blog

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.

SteadyCron dotnethangfirequartzscheduling

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.