Drei Hangfire-Exceptions machen einen grossen Teil der Fragen in Foren und auf Stack Overflow aus — und jede hat eine überschaubare Zahl echter Ursachen. Hier steht, was sie bedeuten, wie Sie herausfinden, welche Ursache bei Ihnen zutrifft, und wie Sie sie beheben. Der genaue Wortlaut kann sich je nach Hangfire-Version und Speicher leicht unterscheiden.
System.InvalidOperationException: JobStorage.Current property value has not been initialized.
You must set it before using Hangfire Client or Server API.
Was es bedeutet: Sie haben einen der statischen Einstiegspunkte von Hangfire verwendet — BackgroundJob.Enqueue, RecurringJob.AddOrUpdate, BackgroundJob.Schedule —, bevor Hangfire wusste, welcher Speicher zu nutzen ist. Die statischen Klassen lesen das globale JobStorage.Current, und das war noch nicht gesetzt.
Typische Ursachen:
Program.cs direkt nach builder.Build() registriert. Mit AddHangfire(...) läuft der Konfigurations-Callback erst, wenn Hangfires Dienste zum ersten Mal aufgelöst werden — nicht beim Aufruf von AddHangfire.Lösung: die injizierbaren Schnittstellen verwenden. Sie lösen den Speicher über die Dependency Injection auf, die Konfiguration ist damit garantiert gelaufen:
var recurring = app.Services.GetRequiredService<IRecurringJobManager>();
recurring.AddOrUpdate<IReportJob>("nightly-report", job => job.RunAsync(), "0 3 * * *");
// in eigenen Services: IBackgroundJobClient injizieren statt BackgroundJob.Enqueue aufzurufen
In einem Prozess ohne Dependency Injection den Speicher vor dem ersten Aufruf explizit konfigurieren, etwa mit GlobalConfiguration.Configuration.UseSqlServerStorage(connectionString). In Tests IBackgroundJobClient mocken — ein weiterer Grund für die Schnittstellen.
Hangfire.Storage.DistributedLockTimeoutException: Timeout expired. The timeout elapsed prior to
obtaining a distributed lock on the '...' resource.
Was es bedeutet: Hangfire wollte eine Sperre im Speicher setzen und hat sie innerhalb des Timeouts nicht bekommen. Der Ressourcenname in der Meldung verrät, um welche Sperre es geht — der wertvollste Hinweis, den Sie haben.
Typische Ursachen:
[DisableConcurrentExecution] an einem langen Job. Das Attribut lässt eine zweite Ausführung auf die Sperre warten. Dauert die laufende Ausführung länger als timeoutInSeconds, wirft die wartende — und wird wiederholt, was sich aufstauen kann. Enthält der Ressourcenname Typ und Methode Ihres Jobs, ist das fast sicher die Ursache.Lösung: Beim Attribut entscheiden, was Sie wirklich wollen. Sollen überlappende Läufe verhindert werden und ist Warten sinnlos, den Timeout nahe null setzen und den zweiten Lauf schnell scheitern lassen, oder zu Beginn des Jobs prüfen, ob die Arbeit schon erledigt ist. Läuft der Job einfach länger als gedacht, den Timeout über seine realistische Höchstdauer anheben. Bei Konkurrenz im Speicher zuerst die Datenbank ansehen (blockierende Sitzungen, langsame Abfragen) und wiederkehrende Zeitpläne verteilen, statt alles um 0 0 * * * zu starten — der Cron-Ausdruck-Explainer hilft zu prüfen, was wann läuft.
Hangfire.Common.JobLoadException: Could not load the job. See inner exception for the details.
Was es bedeutet: Hangfire speichert einen Job als Typname, Methodenname und serialisierte Argumente. Holt ein Worker ihn ab, muss er genau diesen Typ und diese Methode wiederfinden. Die innere Exception sagt, was fehlte — meist eine Assembly, die nicht geladen werden konnte, ein nicht gefundener Typ oder eine Methode, deren Signatur nicht mehr passt.
Typische Ursachen:
Lösung: Job-Signaturen wie eine öffentliche API behandeln. Gegen Schnittstellen einreihen (BackgroundJob.Enqueue<IInvoiceJob>(...)), damit Implementierungen frei umziehen können; müssen sich Parameter ändern, eine neue Methode ergänzen, deployen und die alte erst entfernen, wenn die Queue leer ist. Für verschiedene Anwendungen eigene Queues ([Queue("reports")] plus passende Server-Optionen) oder getrennten Speicher verwenden. Bestehende kaputte Jobs lassen sich nach dem Fix im Dashboard löschen oder neu einreihen.
Diese drei Fehler sind der einfache Fall: Es gibt eine Exception mit einer Meldung, nach der man suchen kann. Die schwierigeren Hangfire-Ausfälle erzeugen gar keine Exception — ein Server, der nach einem Deploy nicht mehr läuft, Jobs, die in Enqueued hängen, ein wiederkehrender Job, der einfach nie auslöst, oder ein Job, der über zwei Tage zehnmal scheitert und in Failed landet, wo niemand hinsieht. Und sobald erfolgreiche Jobs nach etwa einem Tag verfallen, ist auch der Nachweis weg, was gelaufen ist (warum).
Genau diese Lücke schliesst QueueHawk für Hangfire: Jeder Zustandswechsel wird aus Ihrer Anwendung in eine durchsuchbare Historie gestreamt, die Deploys übersteht — inklusive Exception und Stacktrace jedes fehlgeschlagenen Versuchs —, und Alert-Regeln melden steigende Fehlerraten und ausbleibende Heartbeats der Server einer Anwendung.
Eine statische Hangfire-API wie BackgroundJob.Enqueue oder RecurringJob.AddOrUpdate wurde aufgerufen, bevor ein Job-Speicher konfiguriert war. In ASP.NET Core besser IBackgroundJobClient oder IRecurringJobManager aus der Dependency Injection holen statt der statischen Klassen, oder sicherstellen, dass der Speicher vor dem ersten Aufruf konfiguriert ist.
Hangfire konnte innerhalb des Timeouts keine Sperre im Speicher erhalten. Häufige Ursachen sind [DisableConcurrentExecution] an einem Job, der länger läuft als der Timeout des Attributs, starke Blockierungen in der Speicherdatenbank und viele Server, die um dieselbe Ressource konkurrieren.
Jobs werden als Typname, Methode und serialisierte Argumente gespeichert. Verweist ein vor dem Deploy eingereihter Job auf einen Typ, Namespace, eine Assembly oder Methodensignatur, die es in der neuen Version — oder im Serverprozess, der ihn abholt — nicht mehr gibt, kann Hangfire ihn nicht laden.
Hangfire-Job-Historie, die Deploys übersteht, plus Fehlerraten- und Heartbeat-Alerts. Kostenlos für eine Application.