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
- Use a period of at least TimeSpan.FromMilliseconds(1).
- 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.
- 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
- Remember WinRT timers have 1ms minimum resolution; clamp all periods.
- Do not port sub-ms desktop Rx schedules unchanged to UWP/WinRT.
- Centralize interval construction so clamping happens in one place.
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
- period
- WINRT_NO_SUB1MS_TIMERS (Parameter 'period')
- ArgumentOutOfRangeException: period (Specified argument was…
- period (Parameter 'period')
- throw new ArgumentOutOfRangeException(nameof(period));
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)