// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT License. // See the LICENSE file in the project root for more information. extern alias SystemReactiveNet; using System.ComponentModel; using Windows.System.Threading; using SystemReactiveNet::System.Reactive.Concurrency; using System.Reactive.Uwp; using System.Reactive.WindowsRuntime; namespace System.Reactive.Concurrency { /// /// Represents an object that schedules units of work on the Windows Runtime thread pool. /// /// Singleton instance of this type exposed through this static property. [CLSCompliant(false)] public sealed class ThreadPoolScheduler : LocalScheduler, ISchedulerPeriodic, ISchedulerPeriodNoSubMs { #pragma warning disable CS0618 // Type or member is obsolete. The non-UWP ThreadPoolScheduler (which will eventually supersede this) defines the zero-args constructor as private, so it's only the accessibility of "public" that is obsolete, not the presence of the constructor. So this warning is spurious in this particular case. private static readonly Lazy LazyDefault = new(static () => new ThreadPoolScheduler()); #pragma warning restore CS0618 /// /// Constructs a ThreadPoolScheduler that schedules units of work on the Windows ThreadPool. /// [Obsolete("If you require the UWP-specific features of ThreadPoolScheduler use the UwpThreadPoolScheduler in the System.Reactive.For.Uwp package. Otherwise, use the Instance property, because this constructor will be removed in a future version (because UWP applications will end up with the same ThreadPoolScheduler as all other application types).")] public ThreadPoolScheduler() { } /// /// Constructs a ThreadPoolScheduler that schedules units of work on the Windows ThreadPool with the given priority. /// /// Priority for scheduled units of work. [Obsolete("If you require the UWP-specific features of ThreadPoolScheduler use the UwpThreadPoolScheduler in the System.Reactive.For.Uwp package. Otherwise, use the Instance property, because this constructor will be removed in a future version (because UWP applications will end up with the same ThreadPoolScheduler as all other application types).")] public ThreadPoolScheduler(WorkItemPriority priority) { Priority = priority; Options = WorkItemOptions.None; } /// /// Constructs a ThreadPoolScheduler that schedules units of work on the Windows ThreadPool with the given priority. /// /// Priority for scheduled units of work. /// Options that configure how work is scheduled. [Obsolete("If you require the UWP-specific features of ThreadPoolScheduler use the UwpThreadPoolScheduler in the System.Reactive.For.Uwp package. Otherwise, use the Instance property, because this constructor will be removed in a future version (because UWP applications will end up with the same ThreadPoolScheduler as all other application types).")] public ThreadPoolScheduler(WorkItemPriority priority, WorkItemOptions options) { Priority = priority; Options = options; } /// /// Gets the singleton instance of the Windows Runtime thread pool scheduler. /// [Obsolete("Use the Instance property", false)] [EditorBrowsable(EditorBrowsableState.Never)] public static ThreadPoolScheduler Default => LazyDefault.Value; /// /// Gets the singleton instance of the Windows Runtime thread pool scheduler. /// public static ThreadPoolScheduler Instance => LazyDefault.Value; /// /// Gets the priority at which work is scheduled. /// [Obsolete("If you require the UWP-specific features of ThreadPoolScheduler use the UwpThreadPoolScheduler in the System.Reactive.For.Uwp package. This property will be removed in a future version (because UWP applications will end up with the same ThreadPoolScheduler as all other application types).")] public WorkItemPriority Priority { get; } /// /// Gets the options that configure how work is scheduled. /// [Obsolete("If you require the UWP-specific features of ThreadPoolScheduler use the UwpThreadPoolScheduler in the System.Reactive.For.Uwp package. This property will be removed in a future version (because UWP applications will end up with the same ThreadPoolScheduler as all other application types).")] public WorkItemOptions Options { get; } /// /// Schedules an action to be executed. /// /// The type of the state passed to the scheduled action. /// State passed to the action to be executed. /// Action to be executed. /// The disposable object used to cancel the scheduled action (best effort). /// is null. public override IDisposable Schedule(TState state, Func action) { if (action == null) throw new ArgumentNullException(nameof(action)); var userWorkItem = new UserWorkItem(this, state, action); #pragma warning disable CS0618 // Type or member is obsolete. // A note on obsolescence: // The compiler complains because this uses Priority and Options. We could mark the // whole method as obsolete, but this would be slightly misleading because when we // eventually remove the obsoleted UWP support, this whole ThreadPoolScheduler will // be replaced by the non-UWP implementation, and that continues to support this // Schedule overload. So the method isn't really obsolete - it will continue to be // available to UWP apps even after we've removed all UWP-specific code from // System.Reactive. // An argument in favour of marking the method as Obsolete anyway is that the // behaviour will change once we remove UWP code from System.Reactive. However, // the change in behaviour is interesting only if you've specified either // priority or options for the work items, and all the public methods we supply // for that *are* obsolete. So anyone relying on that behaviour will already have // received an obsolescence warning, and should move to UwpThreadPoolScheduler. // Code that left these with the default values should not be affected by the // change to the non-UWP ThreadPoolScheduler, so it would be irksome for them // to get an obsolescence warning, particularly since there isn't actually // anything they can do about it. If they want to continue using this type in // the full knowledge that in a future version that means they'll get the // non-UWP version, we want to let them. var res = ThreadPool.RunAsync( iaa => userWorkItem.Run(), Priority, Options); #pragma warning restore CS0618 // Type or member is obsolete userWorkItem.CancelQueueDisposable = res.AsDisposable(); return userWorkItem; } /// /// Schedules an action to be executed after dueTime, using a Windows.System.Threading.ThreadPoolTimer object. /// /// The type of the state passed to the scheduled action. /// State passed to the action to be executed. /// Action to be executed. /// Relative time after which to execute the action. /// The disposable object used to cancel the scheduled action (best effort). /// is null. public override IDisposable Schedule(TState state, TimeSpan dueTime, Func action) { if (action == null) throw new ArgumentNullException(nameof(action)); var dt = Scheduler.Normalize(dueTime); if (dt.Ticks == 0) { return Schedule(state, action); } return ScheduleSlow(state, dt, action); } private IDisposable ScheduleSlow(TState state, TimeSpan dueTime, Func action) { var userWorkItem = new UserWorkItem(this, state, action); var res = ThreadPoolTimer.CreateTimer( tpt => userWorkItem.Run(), dueTime); userWorkItem.CancelQueueDisposable = res.AsDisposable(); return userWorkItem; } /// /// Schedules a periodic piece of work, using a Windows.System.Threading.ThreadPoolTimer object. /// /// The type of the state passed to the scheduled action. /// Initial state passed to the action upon the first iteration. /// Period for running the work periodically. /// Action to be executed, potentially updating the state. /// The disposable object used to cancel the scheduled recurring action (best effort). /// is null. /// is less than one millisecond. public IDisposable SchedulePeriodic(TState state, TimeSpan period, Func 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(state, period, action); } private sealed class PeriodicallyScheduledWorkItem : IDisposable { private TState _state; private Func _action; private readonly ThreadPoolTimer _timer; private readonly AsyncLock _gate = new(); public PeriodicallyScheduledWorkItem(TState state, TimeSpan period, Func action) { _state = state; _action = action; _timer = ThreadPoolTimer.CreatePeriodicTimer( Tick, period); } private void Tick(ThreadPoolTimer timer) { _gate.Wait( this, static @this => @this._state = @this._action(@this._state)); } public void Dispose() { _timer.Cancel(); _gate.Dispose(); _action = Stubs.I; } } } }