{"record":{"id":"f4341a049144290f","repo":"dotnet/reactive","slug":"argumentoutofrangeexception-period-winrt-no-sub1ms-timers","errorCode":null,"errorMessage":"ArgumentOutOfRangeException: period (WINRT_NO_SUB1MS_TIMERS: The WinRT thread pool cannot handle timers with a period of less than 1ms.)","messagePattern":"ArgumentOutOfRangeException: period \\(WINRT_NO_SUB1MS_TIMERS: The WinRT thread pool cannot handle timers with a period of less than 1ms\\.\\)","errorType":"validation","errorClass":"ArgumentOutOfRangeException","httpStatus":null,"severity":"error","filePath":"Rx.NET/Source/src/System.Reactive.WindowsRuntime/System.Reactive.Concurrency/WindowsRuntimeThreadPoolScheduler.cs","lineNumber":152,"sourceCode":"        /// Schedules a periodic piece of work, using a Windows.System.Threading.ThreadPoolTimer object.\n        /// </summary>\n        /// <typeparam name=\"TState\">The type of the state passed to the scheduled action.</typeparam>\n        /// <param name=\"state\">Initial state passed to the action upon the first iteration.</param>\n        /// <param name=\"period\">Period for running the work periodically.</param>\n        /// <param name=\"action\">Action to be executed, potentially updating the state.</param>\n        /// <returns>The disposable object used to cancel the scheduled recurring action (best effort).</returns>\n        /// <exception cref=\"ArgumentNullException\"><paramref name=\"action\"/> is null.</exception>\n        /// <exception cref=\"ArgumentOutOfRangeException\"><paramref name=\"period\"/> is less than one millisecond.</exception>\n        public IDisposable SchedulePeriodic<TState>(TState state, TimeSpan period, Func<TState, TState> action)\n        {\n            //\n            // The WinRT thread pool is based on the Win32 thread pool and cannot handle\n            // sub-1ms resolution. When passing a lower period, we get single-shot\n            // timer behavior instead. See MSDN documentation for CreatePeriodicTimer\n            // for more information.\n            //\n            if (period < TimeSpan.FromMilliseconds(1))\n                throw new ArgumentOutOfRangeException(nameof(period), Strings_PlatformServices.WINRT_NO_SUB1MS_TIMERS);\n            if (action == null)\n                throw new ArgumentNullException(nameof(action));\n\n            return new PeriodicallyScheduledWorkItem<TState>(state, period, action);\n        }\n\n        private sealed class PeriodicallyScheduledWorkItem<TState> : IDisposable\n        {\n            private TState _state;\n            private Func<TState, TState> _action;\n\n            private readonly ThreadPoolTimer _timer;\n            private readonly AsyncLock _gate = new();\n\n            public PeriodicallyScheduledWorkItem(TState state, TimeSpan period, Func<TState, TState> action)\n            {\n                _state = state;\n                _action = action;","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/dotnet/reactive/blob/94b5d5ab912789f5abe9a72138a25bbd716fe59c/Rx.NET/Source/src/System.Reactive.WindowsRuntime/System.Reactive.Concurrency/WindowsRuntimeThreadPoolScheduler.cs#L134-L170","documentation":"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.","triggerScenarios":"Calling SchedulePeriodic with period = TimeSpan.Zero, TimeSpan.FromMilliseconds(0.5), TimeSpan.FromTicks(n) where n < 10000, or a computed sub-millisecond interval.","commonSituations":"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.","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))."],"exampleFix":"// before\nscheduler.SchedulePeriodic(state, TimeSpan.FromMicroseconds(100), action);\n// after\nvar minPeriod = TimeSpan.FromMilliseconds(1);\nscheduler.SchedulePeriodic(state, period < minPeriod ? minPeriod : period, action);","handlingStrategy":"validation","validationCode":"var min = TimeSpan.FromMilliseconds(1);\nif (period < min) period = min; // clamp before SchedulePeriodic","typeGuard":"bool IsWinRTValidPeriod(TimeSpan p) => p >= TimeSpan.FromMilliseconds(1);","tryCatchPattern":"try { scheduler.SchedulePeriodic(state, period, action); }\ncatch (ArgumentOutOfRangeException ex) when (ex.ParamName == \"period\") { period = TimeSpan.FromMilliseconds(1); /* retry clamped */ }","preventionTips":["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."],"tags":["csharp","scheduler","timer","argument-out-of-range","winrt"],"backgroundTag":"argument-out-of-range","analyzedSha":"94b5d5ab912789f5abe9a72138a25bbd716fe59c","analyzedAt":"2026-09-15T02:26:24.759Z","contentChangedAt":"2026-09-15T02:26:24.759Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}