dotnet/reactive · error · InvalidOperationException

AdvanceTo cannot be called when the scheduler is already…

Error message

AdvanceTo cannot be called when the scheduler is already running. Try using Sleep instead.

What it means

AdvanceTo runs a dispatch loop and therefore requires the scheduler not to be running. If the scheduler is already enabled (IsEnabled == true, e.g. the call is made inside a scheduled action or from within a prior Start/AdvanceTo loop), a nested dispatch loop would occur, so an InvalidOperationException is thrown with this message.

Solutions

  1. Use scheduler.Sleep(relativeTime) to slip time while the scheduler is running, as the message suggests.
  2. Move the AdvanceTo call outside the scheduled work — schedule a new action or advance after the current loop finishes.
  3. Restructure test code so clock advancement happens only from the outer test thread when IsEnabled is false.

Example fix

// before
scheduler.Schedule(() => scheduler.AdvanceTo(targetTime)); // inside running scheduler
// after
scheduler.Schedule(() => scheduler.Sleep(TimeSpan.FromMilliseconds(50)));
Defensive patterns

Strategy: validation

Validate before calling

if (scheduler.IsEnabled)
    scheduler.Sleep(relativeSlip);
else
    scheduler.AdvanceTo(time);

Type guard

bool CanAdvance<TAbsolute, TRelative>(VirtualTimeSchedulerBase<TAbsolute, TRelative> s) => !s.IsEnabled;

Try / catch

try { scheduler.AdvanceTo(time); } catch (InvalidOperationException ex) when (ex.Message.Contains(nameof(VirtualTimeSchedulerBase<TAbsolute, TRelative>.AdvanceTo))) { scheduler.Sleep(remainingSlip); }

Prevention

When it happens

Trigger: Calling AdvanceTo from inside work executed by the same virtual scheduler (a scheduled action re-advancing time), or calling AdvanceTo/Start re-entrantly while a dispatch loop is active.

Common situations: Test actions that schedule more work and try to advance the clock from within the callback; event handlers triggered by scheduled work that call back into the test harness; Rx operators that schedule follow-up work and user code hooks into it.

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/9a40af117163938f. Report an issue: GitHub.

Appendix: source

Thrown at Rx.NET/Source/src/System.Reactive/Concurrency/VirtualTimeScheduler.cs:241

                    {
                        if (Comparer.Compare(next.DueTime, Clock) > 0)
                        {
                            Clock = next.DueTime;
                        }

                        next.Invoke();
                    }
                    else
                    {
                        IsEnabled = false;
                    }
                } while (IsEnabled);

                Clock = time;
            }
            else
            {
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings_Linq.CANT_ADVANCE_WHILE_RUNNING, nameof(AdvanceTo)));
            }
        }

        /// <summary>
        /// Advances the scheduler's clock by the specified relative time, running all work scheduled for that timespan.
        /// </summary>
        /// <param name="time">Relative time to advance the scheduler's clock by.</param>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="time"/> is negative.</exception>
        /// <exception cref="InvalidOperationException">The scheduler is already running. VirtualTimeScheduler doesn't support running nested work dispatch loops. To simulate time slippage while running work on the scheduler, use <see cref="Sleep"/>.</exception>
        public void AdvanceBy(TRelative time)
        {
            var dt = Add(Clock, time);

            var dueToClock = Comparer.Compare(dt, Clock);
            if (dueToClock < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(time));
            }

View on GitHub (pinned to 94b5d5ab91)