dotnet/reactive · error · ArgumentOutOfRangeException

period

Error message

period

What it means

CoreDispatcherScheduler.SchedulePeriodic<TState>(state, period, action) throws ArgumentOutOfRangeException because 'period' is negative. A periodic timer cannot run at a negative interval; the WinRT dispatcher timer only accepts TimeSpan.Zero or positive periods, so Rx rejects negative values eagerly.

Solutions

  1. Pass a positive (or zero) TimeSpan as 'period'; clamp negative values with something like period < TimeSpan.Zero ? TimeSpan.Zero : period.
  2. Fix the computation producing the negative duration (e.g. use Math.Max(TimeSpan.Zero, target - now)).
  3. Validate configured interval values at startup before scheduling.

Example fix

// before
var elapsed = DateTime.Now - dueTime; // negative when dueTime is in the future
scheduler.SchedulePeriodic(0, elapsed, Tick);
// after
var period = DateTime.Now < dueTime ? dueTime - DateTime.Now : TimeSpan.Zero;
scheduler.SchedulePeriodic(0, period, Tick);
Defensive patterns

Strategy: validation

Validate before calling

// csharp
if (period < TimeSpan.Zero)
    period = TimeSpan.Zero; // or throw with your own message
scheduler.SchedulePeriodic(state, period, action);

Type guard

null

Try / catch

// csharp
try { d = scheduler.SchedulePeriodic(state, period, action); }
catch (ArgumentOutOfRangeException ex) when (ex.ParamName == "period") { log.Error("Negative periodic period", ex); d = Disposable.Empty; }

Prevention

When it happens

Trigger: Calling SchedulePeriodic with a negative TimeSpan, e.g. SchedulePeriodic(state, TimeSpan.FromSeconds(-1), action), often from a computed duration like a negative difference between DateTimes or a misconfigured setting.

Common situations: Computing the period from (target - DateTime.Now) after the target has already passed; config/app-setting values parsed with a minus sign; arithmetic sign errors when building polling intervals.

Related errors


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

Appendix: source

Thrown at Rx.NET/Source/src/System.Reactive/Platforms/WinRT/Concurrency/CoreDispatcherScheduler.cs:234

        /// <summary>
        /// Schedules a periodic piece of work on the dispatcher, using a <see cref="DispatcherQueueTimer"/> 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)
        {
            //
            // According to MSDN documentation, the default is TimeSpan.Zero, so that's definitely valid.
            // Empirical observation - negative values seem to be normalized to TimeSpan.Zero, but let's not go there.
            //
            if (period < TimeSpan.Zero)
            {
                throw new ArgumentOutOfRangeException(nameof(period));
            }

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

            var timer = CreateDispatcherQueue().CreateTimer();

            var state1 = state;

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

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

View on GitHub (pinned to 94b5d5ab91)