dotnet/reactive · error · ArgumentOutOfRangeException

Specified argument was out of the range of valid values…

Error message

Specified argument was out of the range of valid values. (Parameter 'period')

What it means

DispatcherScheduler.SchedulePeriodic throws ArgumentOutOfRangeException when the period is less than TimeSpan.Zero. A negative periodic interval is meaningless; zero is accepted by this check (the guard is strictly < Zero), but negative values are rejected before the action null-check.

Solutions

  1. Clamp the period before scheduling: if (period < TimeSpan.Zero) period = TimeSpan.Zero;.
  2. Validate configuration values for polling intervals at startup and reject negatives early.
  3. Use Math.Max(TimeSpan.Zero, computedPeriod) for derived periods.

Example fix

// before
scheduler.SchedulePeriodic(state, period, action); // period may be negative
// after
var safePeriod = period < TimeSpan.Zero ? TimeSpan.Zero : period;
scheduler.SchedulePeriodic(state, safePeriod, action);
Defensive patterns

Strategy: validation

Validate before calling

if (period < TimeSpan.Zero) throw new InvalidOperationException($"period must be >= TimeSpan.Zero, was {period}");

Type guard

bool IsValidPeriod(TimeSpan p) => p >= TimeSpan.Zero;

Try / catch

try { scheduler.SchedulePeriodic(state, period, action); } catch (ArgumentOutOfRangeException ex) when (ex.ParamName == "period") { /* clamp and retry */ }

Prevention

When it happens

Trigger: scheduler.SchedulePeriodic(state, TimeSpan.FromMilliseconds(-1), action) or a computed period from config/math that ends up negative (e.g. subtracting timestamps, misconfigured interval).

Common situations: Reading an interval from app config where the value is negative; computing period = next - now after `now` already passed; typos like FromSeconds(-1) in tests.

Related errors


AI-assisted analysis of dotnet/reactive@94b5d5ab91 (2026-09-15). Data as JSON: /api/errors/e213dfe4e4c9f4bd. Report an issue: GitHub.

Appendix: source

Thrown at Rx.NET/Source/src/System.Reactive.Wpf/System.Reactive.Concurrency/DispatcherScheduler.cs:184

            return d;
        }

        /// <summary>
        /// Schedules a periodic piece of work on the dispatcher, using a <see cref="System.Windows.Threading.DispatcherTimer"/> object.
        /// </summary>
        /// <typeparam name="TState">The type of the state passed to the scheduled action.</typeparam>
        /// <param name="state">Initial state passed to the action upon the first iteration.</param>
        /// <param name="period">Period for running the work periodically.</param>
        /// <param name="action">Action to be executed, potentially updating the state.</param>
        /// <returns>The disposable object used to cancel the scheduled recurring action (best effort).</returns>
        /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c>.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="period"/> is less than <see cref="TimeSpan.Zero"/>.</exception>
        public IDisposable SchedulePeriodic<TState>(TState state, TimeSpan period, Func<TState, TState> action)
        {
            if (period < TimeSpan.Zero)
            {
                throw new ArgumentOutOfRangeException(nameof(period));
            }

            if (action == null)
            {
                throw new ArgumentNullException(nameof(action));
            }

            var timer = new System.Windows.Threading.DispatcherTimer(Priority, Dispatcher);

            var state1 = state;

            timer.Tick += (s, e) =>
            {
                state1 = action(state1);
            };

            timer.Interval = period;
            timer.Start();

View on GitHub (pinned to 94b5d5ab91)