dotnet/reactive · error · ObjectDisposedException
EventLoopScheduler
Error message
EventLoopScheduler
What it means
EventLoopScheduler.Schedule<TState>(state, dueTime, action) throws ObjectDisposedException(nameof(EventLoopScheduler)) when the action is enqueued after the scheduler was disposed. Disposal sets _disposed under _gate; any new work would never run because the event loop thread has exited, so the scheduler rejects it explicitly.
Solutions
- Dispose subscriptions (IDsisposable from Subscribe) before disposing the scheduler.
- Serialize shutdown: cancel/signal work first, then dispose the scheduler last.
- Wrap scheduling calls and swallow/ignore ObjectDisposedException during shutdown where acceptable.
- Create a new EventLoopScheduler if you intentionally disposed and need to schedule again — it cannot be revived.
Example fix
// before scheduler.Dispose(); scheduler.Schedule(0, TimeSpan.Zero, s => Disposable.Empty); // throws // after subscription.Dispose(); scheduler.Dispose(); if (!scheduler.IsDisposed) scheduler.Schedule(0, TimeSpan.Zero, s => Disposable.Empty);
Defensive patterns
Strategy: try-catch
Validate before calling
if (schedulerIsDisposed) return Disposable.Empty; // track disposal yourself scheduler.Schedule(state, dueTime, action);
Try / catch
try { return scheduler.Schedule(state, dueTime, action); }
catch (ObjectDisposedException)
{
return Disposable.Empty; // benign during shutdown
} Prevention
- Dispose Rx subscriptions before disposing the scheduler
- Use a shutdown barrier/Task to wait for in-flight work before Dispose
- Track scheduler lifetime with a CancellationToken passed to work items
When it happens
Trigger: Calling any Schedule/SchedulePeriodic overload on an EventLoopScheduler after Dispose() was called — typically a long-lived subscription or timer whose observer fires after shutdown, or a periodic work item re-scheduling itself post-dispose.
Common situations: Application shutdown / DI container disposal order issues where the scheduler is disposed while active Rx subscriptions still attempt to schedule; unit tests disposing the scheduler in TearDown while async callbacks are still pending.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- RefCountDisposable
- Cannot access a disposed object.
- null (Parameter 'scheduler')
- Value cannot be null. (Parameter 'observer')
- Value cannot be null. (Parameter 'values')
AI-assisted analysis of dotnet/reactive@94b5d5ab91 (2026-09-15).
Data as JSON: /api/errors/fa81e3e1a4a2a107.
Report an issue: GitHub.
Appendix: source
Thrown at Rx.NET/Source/src/System.Reactive/Concurrency/EventLoopScheduler.cs:151
/// <param name="dueTime">Relative time after which to execute the action.</param>
/// <returns>The disposable object used to cancel the scheduled action (best effort).</returns>
/// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception>
/// <exception cref="ObjectDisposedException">The scheduler has been disposed and doesn't accept new work.</exception>
public override IDisposable Schedule<TState>(TState state, TimeSpan dueTime, Func<IScheduler, TState, IDisposable> action)
{
if (action == null)
{
throw new ArgumentNullException(nameof(action));
}
var due = _stopwatch.Elapsed + dueTime;
var si = new ScheduledItem<TimeSpan, TState>(this, state, action, due);
lock (_gate)
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(EventLoopScheduler));
}
if (dueTime <= TimeSpan.Zero)
{
_readyList.Enqueue(si);
_evt.Release();
}
else
{
_queue.Enqueue(si);
_evt.Release();
}
EnsureThread();
}
return si;
}View on GitHub (pinned to 94b5d5ab91)