dotnet/reactive · error · ArgumentOutOfRangeException

ArgumentOutOfRangeException: period…

Error message

ArgumentOutOfRangeException: period (WINRT_NO_SUB1MS_TIMERS: The WinRT thread pool cannot handle timers with a period of less than 1ms.)

What it means

WindowsRuntimeThreadPoolScheduler.SchedulePeriodic rejects periods shorter than 1 millisecond with ArgumentOutOfRangeException('period'). The WinRT thread pool (Win32 CreatePeriodicTimer based) has no sub-1ms resolution; lower values silently degrade to single-shot timer behavior, so the library fails fast with the WINRT_NO_SUB1MS_TIMERS message.

Solutions

  1. Use a period of at least TimeSpan.FromMilliseconds(1).
  2. If sub-ms granularity is required, reschedule single-shot work (Schedule with a Stopwatch-driven due time) or use a spin/hybrid loop outside the WinRT pool.
  3. Clamp configured intervals: period = TimeSpan.FromTicks(Math.Max(period.Ticks, TimeSpan.FromMilliseconds(1).Ticks)).

Example fix

// before
scheduler.SchedulePeriodic(state, TimeSpan.FromMicroseconds(100), action);
// after
var minPeriod = TimeSpan.FromMilliseconds(1);
scheduler.SchedulePeriodic(state, period < minPeriod ? minPeriod : period, action);
Defensive patterns

Strategy: validation

Validate before calling

var min = TimeSpan.FromMilliseconds(1);
if (period < min) period = min; // clamp before SchedulePeriodic

Type guard

bool IsWinRTValidPeriod(TimeSpan p) => p >= TimeSpan.FromMilliseconds(1);

Try / catch

try { scheduler.SchedulePeriodic(state, period, action); }
catch (ArgumentOutOfRangeException ex) when (ex.ParamName == "period") { period = TimeSpan.FromMilliseconds(1); /* retry clamped */ }

Prevention

When it happens

Trigger: Calling SchedulePeriodic with period = TimeSpan.Zero, TimeSpan.FromMilliseconds(0.5), TimeSpan.FromTicks(n) where n < 10000, or a computed sub-millisecond interval.

Common situations: High-frequency polling designs ported from desktop Rx where System.Reactive's default scheduler supports 1ms+ ticks; configuring intervals from config values specified in microseconds; forgetting the WinRT platform limitation.

Related errors


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

Appendix: source

Thrown at Rx.NET/Source/src/System.Reactive.WindowsRuntime/System.Reactive.Concurrency/WindowsRuntimeThreadPoolScheduler.cs:152

        /// Schedules a periodic piece of work, using a Windows.System.Threading.ThreadPoolTimer 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 null.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="period"/> is less than one millisecond.</exception>
        public IDisposable SchedulePeriodic<TState>(TState state, TimeSpan period, Func<TState, TState> action)
        {
            //
            // The WinRT thread pool is based on the Win32 thread pool and cannot handle
            // sub-1ms resolution. When passing a lower period, we get single-shot
            // timer behavior instead. See MSDN documentation for CreatePeriodicTimer
            // for more information.
            //
            if (period < TimeSpan.FromMilliseconds(1))
                throw new ArgumentOutOfRangeException(nameof(period), Strings_PlatformServices.WINRT_NO_SUB1MS_TIMERS);
            if (action == null)
                throw new ArgumentNullException(nameof(action));

            return new PeriodicallyScheduledWorkItem<TState>(state, period, action);
        }

        private sealed class PeriodicallyScheduledWorkItem<TState> : IDisposable
        {
            private TState _state;
            private Func<TState, TState> _action;

            private readonly ThreadPoolTimer _timer;
            private readonly AsyncLock _gate = new();

            public PeriodicallyScheduledWorkItem(TState state, TimeSpan period, Func<TState, TState> action)
            {
                _state = state;
                _action = action;

View on GitHub (pinned to 94b5d5ab91)