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

  1. Stop all consumers of the scheduler before calling Dispose(); use a cancellation token to wind down worker queries.
  2. Catch ObjectDisposedException at call sites that may race with shutdown and treat it as 'no tasks'.
  3. 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

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


AI-assisted analysis of HangfireIO/Hangfire@c236dd0f93 (2026-08-13). Data as JSON: /api/errors/3d77868c2a0423ad. Report an issue: GitHub.