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

  1. Dispose subscriptions (IDsisposable from Subscribe) before disposing the scheduler.
  2. Serialize shutdown: cancel/signal work first, then dispose the scheduler last.
  3. Wrap scheduling calls and swallow/ignore ObjectDisposedException during shutdown where acceptable.
  4. 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

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


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)