dotnet/reactive · error · ArgumentOutOfRangeException
WINRT_NO_SUB1MS_TIMERS (Parameter 'period')
Error message
WINRT_NO_SUB1MS_TIMERS (Parameter 'period')
What it means
ThreadPoolScheduler.Windows.SchedulePeriodic<TState>(state, period, action) throws ArgumentOutOfRangeException with message 'WINRT_NO_SUB1MS_TIMERS' because 'period' is less than 1 millisecond. The WinRT thread pool is built on the Win32 thread pool and cannot do sub-1ms periodic timers — such values degrade into single-shot timers — so Rx rejects them with a descriptive message instead of silently misbehaving.
Solutions
- Use a period of at least TimeSpan.FromMilliseconds(1); if you need sub-ms periodic work, redesign (batch work, or use a busy loop on a dedicated thread).
- Clamp: period = period < TimeSpan.FromMilliseconds(1) ? TimeSpan.FromMilliseconds(1) : period.
- If a one-shot near-immediate execution is intended, call Schedule(state, TimeSpan.Zero, action) instead of SchedulePeriodic.
Example fix
// before scheduler.SchedulePeriodic(0, TimeSpan.Zero, Tick); // throws WINRT_NO_SUB1MS_TIMERS // after var period = TimeSpan.FromMilliseconds(1); // WinRT minimum scheduler.SchedulePeriodic(0, period, Tick);
Defensive patterns
Strategy: validation
Validate before calling
// csharp
const TimeSpan MinWinrtPeriod = TimeSpan.FromMilliseconds(1);
if (period < MinWinrtPeriod)
period = MinWinrtPeriod; // WinRT thread pool cannot do sub-1ms periodic timers
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($"Period {period} below WinRT 1ms minimum", ex); d = Disposable.Empty; } Prevention
- Always clamp periodic periods to at least 1ms when targeting WinRT/UWP
- Do not assume desktop Rx timer resolution when porting code to WinRT
- If sub-ms frequency is required, use a dedicated busy-wait thread instead of SchedulePeriodic
When it happens
Trigger: Calling SchedulePeriodic with period values like TimeSpan.Zero, TimeSpan.FromTicks(...), TimeSpan.FromMilliseconds(0.5), or a computed sub-millisecond interval (e.g. TimeSpan.FromSeconds(0.0001)).
Common situations: Porting code written for desktop schedulers (which support ~1ms or sub-ms timers) to a UWP/WinRT app; math producing zero-length intervals; attempting high-frequency polling that WinRT cannot support.
Understand the failure class
Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.
Related errors
- ArgumentOutOfRangeException: period (WINRT_NO_SUB1MS_TIMERS:
- period
- period
- ArgumentOutOfRangeException: period (Specified argument was
- ArgumentOutOfRangeException
AI-assisted analysis of dotnet/reactive@94b5d5ab91 (2026-09-15).
Data as JSON: /api/errors/771a4c8d46e0732f.
Report an issue: GitHub.
Appendix: source
Thrown at Rx.NET/Source/src/System.Reactive/Platforms/WinRT/Concurrency/ThreadPoolScheduler.Windows.cs:187
/// 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)