HangfireIO/Hangfire · error · ObjectDisposedException
{GetType().FullName}
Error message
{GetType().FullName} What it means
Thrown by BackgroundTaskScheduler.ThrowIfDisposed (an ObjectDisposedException with the type's full name) when an operation is attempted after Dispose(). The scheduler sets _disposed=1 in Dispose() and releases its wait handles; continued use after that is undefined.
Source
Thrown at src/Hangfire.Core/Processing/BackgroundTaskScheduler.cs:347
handler?.Invoke(exception);
}
#if !NETSTANDARD1_3
catch (Exception ex) when (ex.IsCatchableExceptionType())
{
Trace.WriteLine("Unexpected exception caught in exception handler itself." + Environment.NewLine + ex);
}
#else
catch
{
}
#endif
}
private void ThrowIfDisposed()
{
if (Volatile.Read(ref _disposed) == 1)
{
throw new ObjectDisposedException(GetType().FullName);
}
}
}
}
View on GitHub (pinned to c236dd0f93)
Solutions
- Stop all consumers of the scheduler before calling Dispose(); use a cancellation token to wind down worker queries.
- Catch ObjectDisposedException at call sites that may race with shutdown and treat it as 'no tasks'.
- Avoid calling GetScheduledTasks for diagnostics on a scheduler that may be disposed — snapshot liveness another way.
Example fix
// before
var tasks = scheduler.GetScheduledTasks(); // throws if disposed
// after
IEnumerable<Task> tasks;
try { tasks = scheduler.GetScheduledTasks(); }
catch (ObjectDisposedException) { tasks = Enumerable.Empty<Task>(); } Defensive patterns
Strategy: try-catch
Validate before calling
if (_disposed == 1) return Enumerable.Empty<Task>(); return scheduler.GetScheduledTasks();
Try / catch
IEnumerable<Task> tasks;
try { tasks = scheduler.GetScheduledTasks(); }
catch (ObjectDisposedException) { tasks = Enumerable.Empty<Task>(); } Prevention
- Tear down all consumers (diagnostics, monitors) before disposing the scheduler.
- Use a cancellation token to signal shutdown so queries stop before Dispose().
- Never query a scheduler whose lifetime is owned by a using-block.
When it happens
Trigger: Calling GetScheduledTasks (or any path through ThrowIfDisposed) on a BackgroundTaskScheduler after Dispose() was invoked. Commonly a shutdown race where one thread disposes while another queries scheduled tasks.
Common situations: Application shutdown disposes the scheduler while a diagnostics/monitoring thread enumerates scheduled tasks; a using-block scopes the scheduler too narrowly; double-handling where Dispose runs early due to an exception.
Related errors
- threadFactory
- At least one non-started thread should be created.
- All the threads should be non-null and in the ThreadState.Un
- threadCount
- All the threads should be non-null and in the ThreadState.Un
AI-assisted analysis of HangfireIO/Hangfire@c236dd0f93 (2026-08-13).
Data as JSON: /api/errors/3d77868c2a0423ad.
Report an issue: GitHub.