ThreadPoolScheduler.Windows.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT License.
  3. // See the LICENSE file in the project root for more information.
  4. extern alias SystemReactiveNet;
  5. using System.ComponentModel;
  6. using Windows.System.Threading;
  7. using SystemReactiveNet::System.Reactive.Concurrency;
  8. using System.Reactive.Uwp;
  9. using System.Reactive.WindowsRuntime;
  10. namespace System.Reactive.Concurrency
  11. {
  12. /// <summary>
  13. /// Represents an object that schedules units of work on the Windows Runtime thread pool.
  14. /// </summary>
  15. /// <seealso cref="Default">Singleton instance of this type exposed through this static property.</seealso>
  16. [CLSCompliant(false)]
  17. public sealed class ThreadPoolScheduler : LocalScheduler, ISchedulerPeriodic, ISchedulerPeriodNoSubMs
  18. {
  19. #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.
  20. private static readonly Lazy<ThreadPoolScheduler> LazyDefault = new(static () => new ThreadPoolScheduler());
  21. #pragma warning restore CS0618
  22. /// <summary>
  23. /// Constructs a ThreadPoolScheduler that schedules units of work on the Windows ThreadPool.
  24. /// </summary>
  25. [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).")]
  26. public ThreadPoolScheduler()
  27. {
  28. }
  29. /// <summary>
  30. /// Constructs a ThreadPoolScheduler that schedules units of work on the Windows ThreadPool with the given priority.
  31. /// </summary>
  32. /// <param name="priority">Priority for scheduled units of work.</param>
  33. [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).")]
  34. public ThreadPoolScheduler(WorkItemPriority priority)
  35. {
  36. Priority = priority;
  37. Options = WorkItemOptions.None;
  38. }
  39. /// <summary>
  40. /// Constructs a ThreadPoolScheduler that schedules units of work on the Windows ThreadPool with the given priority.
  41. /// </summary>
  42. /// <param name="priority">Priority for scheduled units of work.</param>
  43. /// <param name="options">Options that configure how work is scheduled.</param>
  44. [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).")]
  45. public ThreadPoolScheduler(WorkItemPriority priority, WorkItemOptions options)
  46. {
  47. Priority = priority;
  48. Options = options;
  49. }
  50. /// <summary>
  51. /// Gets the singleton instance of the Windows Runtime thread pool scheduler.
  52. /// </summary>
  53. [Obsolete("Use the Instance property", false)]
  54. [EditorBrowsable(EditorBrowsableState.Never)]
  55. public static ThreadPoolScheduler Default => LazyDefault.Value;
  56. /// <summary>
  57. /// Gets the singleton instance of the Windows Runtime thread pool scheduler.
  58. /// </summary>
  59. public static ThreadPoolScheduler Instance => LazyDefault.Value;
  60. /// <summary>
  61. /// Gets the priority at which work is scheduled.
  62. /// </summary>
  63. [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).")]
  64. public WorkItemPriority Priority { get; }
  65. /// <summary>
  66. /// Gets the options that configure how work is scheduled.
  67. /// </summary>
  68. [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).")]
  69. public WorkItemOptions Options { get; }
  70. /// <summary>
  71. /// Schedules an action to be executed.
  72. /// </summary>
  73. /// <typeparam name="TState">The type of the state passed to the scheduled action.</typeparam>
  74. /// <param name="state">State passed to the action to be executed.</param>
  75. /// <param name="action">Action to be executed.</param>
  76. /// <returns>The disposable object used to cancel the scheduled action (best effort).</returns>
  77. /// <exception cref="ArgumentNullException"><paramref name="action"/> is null.</exception>
  78. public override IDisposable Schedule<TState>(TState state, Func<IScheduler, TState, IDisposable> action)
  79. {
  80. if (action == null)
  81. throw new ArgumentNullException(nameof(action));
  82. var userWorkItem = new UserWorkItem<TState>(this, state, action);
  83. #pragma warning disable CS0618 // Type or member is obsolete.
  84. // A note on obsolescence:
  85. // The compiler complains because this uses Priority and Options. We could mark the
  86. // whole method as obsolete, but this would be slightly misleading because when we
  87. // eventually remove the obsoleted UWP support, this whole ThreadPoolScheduler will
  88. // be replaced by the non-UWP implementation, and that continues to support this
  89. // Schedule overload. So the method isn't really obsolete - it will continue to be
  90. // available to UWP apps even after we've removed all UWP-specific code from
  91. // System.Reactive.
  92. // An argument in favour of marking the method as Obsolete anyway is that the
  93. // behaviour will change once we remove UWP code from System.Reactive. However,
  94. // the change in behaviour is interesting only if you've specified either
  95. // priority or options for the work items, and all the public methods we supply
  96. // for that *are* obsolete. So anyone relying on that behaviour will already have
  97. // received an obsolescence warning, and should move to UwpThreadPoolScheduler.
  98. // Code that left these with the default values should not be affected by the
  99. // change to the non-UWP ThreadPoolScheduler, so it would be irksome for them
  100. // to get an obsolescence warning, particularly since there isn't actually
  101. // anything they can do about it. If they want to continue using this type in
  102. // the full knowledge that in a future version that means they'll get the
  103. // non-UWP version, we want to let them.
  104. var res = ThreadPool.RunAsync(
  105. iaa => userWorkItem.Run(),
  106. Priority,
  107. Options);
  108. #pragma warning restore CS0618 // Type or member is obsolete
  109. userWorkItem.CancelQueueDisposable = res.AsDisposable();
  110. return userWorkItem;
  111. }
  112. /// <summary>
  113. /// Schedules an action to be executed after dueTime, using a Windows.System.Threading.ThreadPoolTimer object.
  114. /// </summary>
  115. /// <typeparam name="TState">The type of the state passed to the scheduled action.</typeparam>
  116. /// <param name="state">State passed to the action to be executed.</param>
  117. /// <param name="action">Action to be executed.</param>
  118. /// <param name="dueTime">Relative time after which to execute the action.</param>
  119. /// <returns>The disposable object used to cancel the scheduled action (best effort).</returns>
  120. /// <exception cref="ArgumentNullException"><paramref name="action"/> is null.</exception>
  121. public override IDisposable Schedule<TState>(TState state, TimeSpan dueTime, Func<IScheduler, TState, IDisposable> action)
  122. {
  123. if (action == null)
  124. throw new ArgumentNullException(nameof(action));
  125. var dt = Scheduler.Normalize(dueTime);
  126. if (dt.Ticks == 0)
  127. {
  128. return Schedule(state, action);
  129. }
  130. return ScheduleSlow(state, dt, action);
  131. }
  132. private IDisposable ScheduleSlow<TState>(TState state, TimeSpan dueTime, Func<IScheduler, TState, IDisposable> action)
  133. {
  134. var userWorkItem = new UserWorkItem<TState>(this, state, action);
  135. var res = ThreadPoolTimer.CreateTimer(
  136. tpt => userWorkItem.Run(),
  137. dueTime);
  138. userWorkItem.CancelQueueDisposable = res.AsDisposable();
  139. return userWorkItem;
  140. }
  141. /// <summary>
  142. /// Schedules a periodic piece of work, using a Windows.System.Threading.ThreadPoolTimer object.
  143. /// </summary>
  144. /// <typeparam name="TState">The type of the state passed to the scheduled action.</typeparam>
  145. /// <param name="state">Initial state passed to the action upon the first iteration.</param>
  146. /// <param name="period">Period for running the work periodically.</param>
  147. /// <param name="action">Action to be executed, potentially updating the state.</param>
  148. /// <returns>The disposable object used to cancel the scheduled recurring action (best effort).</returns>
  149. /// <exception cref="ArgumentNullException"><paramref name="action"/> is null.</exception>
  150. /// <exception cref="ArgumentOutOfRangeException"><paramref name="period"/> is less than one millisecond.</exception>
  151. public IDisposable SchedulePeriodic<TState>(TState state, TimeSpan period, Func<TState, TState> action)
  152. {
  153. //
  154. // The WinRT thread pool is based on the Win32 thread pool and cannot handle
  155. // sub-1ms resolution. When passing a lower period, we get single-shot
  156. // timer behavior instead. See MSDN documentation for CreatePeriodicTimer
  157. // for more information.
  158. //
  159. if (period < TimeSpan.FromMilliseconds(1))
  160. throw new ArgumentOutOfRangeException(nameof(period), Strings_PlatformServices.WINRT_NO_SUB1MS_TIMERS);
  161. if (action == null)
  162. throw new ArgumentNullException(nameof(action));
  163. return new PeriodicallyScheduledWorkItem<TState>(state, period, action);
  164. }
  165. private sealed class PeriodicallyScheduledWorkItem<TState> : IDisposable
  166. {
  167. private TState _state;
  168. private Func<TState, TState> _action;
  169. private readonly ThreadPoolTimer _timer;
  170. private readonly AsyncLock _gate = new();
  171. public PeriodicallyScheduledWorkItem(TState state, TimeSpan period, Func<TState, TState> action)
  172. {
  173. _state = state;
  174. _action = action;
  175. _timer = ThreadPoolTimer.CreatePeriodicTimer(
  176. Tick,
  177. period);
  178. }
  179. private void Tick(ThreadPoolTimer timer)
  180. {
  181. _gate.Wait(
  182. this,
  183. static @this => @this._state = @this._action(@this._state));
  184. }
  185. public void Dispose()
  186. {
  187. _timer.Cancel();
  188. _gate.Dispose();
  189. _action = Stubs<TState>.I;
  190. }
  191. }
  192. }
  193. }