CoreDispatcherScheduler.cs 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
  2. #if WINDOWS
  3. using System.Reactive.Concurrency;
  4. using System.Reactive.Disposables;
  5. using System.Runtime.ExceptionServices;
  6. using System.Threading;
  7. using Windows.UI.Core;
  8. using Windows.UI.Xaml;
  9. namespace System.Reactive.Concurrency
  10. {
  11. /// <summary>
  12. /// Represents an object that schedules units of work on a Windows.UI.Core.CoreDispatcher.
  13. /// </summary>
  14. /// <remarks>
  15. /// This scheduler type is typically used indirectly through the <see cref="System.Reactive.Linq.DispatcherObservable.ObserveOnDispatcher&lt;TSource&gt;(IObservable&lt;TSource&gt;)"/> and <see cref="System.Reactive.Linq.DispatcherObservable.SubscribeOnDispatcher&lt;TSource&gt;(IObservable&lt;TSource&gt;)"/> methods that use the current Dispatcher.
  16. /// </remarks>
  17. public sealed class CoreDispatcherScheduler : LocalScheduler, ISchedulerPeriodic
  18. {
  19. private readonly CoreDispatcher _dispatcher;
  20. private readonly CoreDispatcherPriority _priority;
  21. /// <summary>
  22. /// Constructs a CoreDispatcherScheduler that schedules units of work on the given Windows.UI.Core.CoreDispatcher.
  23. /// </summary>
  24. /// <param name="dispatcher">Dispatcher to schedule work on.</param>
  25. /// <exception cref="ArgumentNullException"><paramref name="dispatcher"/> is null.</exception>
  26. public CoreDispatcherScheduler(CoreDispatcher dispatcher)
  27. {
  28. if (dispatcher == null)
  29. throw new ArgumentNullException("dispatcher");
  30. _dispatcher = dispatcher;
  31. _priority = CoreDispatcherPriority.Normal;
  32. }
  33. /// <summary>
  34. /// Constructs a CoreDispatcherScheduler that schedules units of work on the given Windows.UI.Core.CoreDispatcher with the given priority.
  35. /// </summary>
  36. /// <param name="dispatcher">Dispatcher to schedule work on.</param>
  37. /// <param name="priority">Priority for scheduled units of work.</param>
  38. /// <exception cref="ArgumentNullException"><paramref name="dispatcher"/> is null.</exception>
  39. public CoreDispatcherScheduler(CoreDispatcher dispatcher, CoreDispatcherPriority priority)
  40. {
  41. if (dispatcher == null)
  42. throw new ArgumentNullException("dispatcher");
  43. _dispatcher = dispatcher;
  44. _priority = priority;
  45. }
  46. /// <summary>
  47. /// Gets the scheduler that schedules work on the Windows.UI.Core.CoreDispatcher associated with the current Window.
  48. /// </summary>
  49. public static CoreDispatcherScheduler Current
  50. {
  51. get
  52. {
  53. var window = Window.Current;
  54. if (window == null)
  55. throw new InvalidOperationException(Strings_WindowsThreading.NO_WINDOW_CURRENT);
  56. return new CoreDispatcherScheduler(window.Dispatcher);
  57. }
  58. }
  59. /// <summary>
  60. /// Gets the Windows.UI.Core.CoreDispatcher associated with the CoreDispatcherScheduler.
  61. /// </summary>
  62. public CoreDispatcher Dispatcher
  63. {
  64. get { return _dispatcher; }
  65. }
  66. /// <summary>
  67. /// Gets the priority at which work is scheduled.
  68. /// </summary>
  69. public CoreDispatcherPriority Priority
  70. {
  71. get { return _priority; }
  72. }
  73. /// <summary>
  74. /// Schedules an action to be executed on the dispatcher.
  75. /// </summary>
  76. /// <typeparam name="TState">The type of the state passed to the scheduled action.</typeparam>
  77. /// <param name="state">State passed to the action to be executed.</param>
  78. /// <param name="action">Action to be executed.</param>
  79. /// <returns>The disposable object used to cancel the scheduled action (best effort).</returns>
  80. /// <exception cref="ArgumentNullException"><paramref name="action"/> is null.</exception>
  81. public override IDisposable Schedule<TState>(TState state, Func<IScheduler, TState, IDisposable> action)
  82. {
  83. if (action == null)
  84. throw new ArgumentNullException("action");
  85. var d = new SingleAssignmentDisposable();
  86. var res = _dispatcher.RunAsync(_priority, () =>
  87. {
  88. if (!d.IsDisposed)
  89. {
  90. try
  91. {
  92. d.Disposable = action(this, state);
  93. }
  94. catch (Exception ex)
  95. {
  96. //
  97. // Work-around for the behavior of throwing from RunAsync not propagating
  98. // the exception to the Application.UnhandledException event (as of W8RP)
  99. // as our users have come to expect from previous XAML stacks using Rx.
  100. //
  101. // If we wouldn't do this, there'd be an observable behavioral difference
  102. // between scheduling with TimeSpan.Zero or using this overload.
  103. //
  104. // For scheduler implementation guidance rules, see TaskPoolScheduler.cs
  105. // in System.Reactive.PlatformServices\Reactive\Concurrency.
  106. //
  107. var timer = new DispatcherTimer();
  108. timer.Interval = TimeSpan.Zero;
  109. timer.Tick += (o, e) =>
  110. {
  111. timer.Stop();
  112. ExceptionDispatchInfo.Capture(ex).Throw();
  113. };
  114. timer.Start();
  115. }
  116. }
  117. });
  118. return new CompositeDisposable(
  119. d,
  120. Disposable.Create(res.Cancel)
  121. );
  122. }
  123. /// <summary>
  124. /// Schedules an action to be executed after dueTime on the dispatcher, using a Windows.UI.Xaml.DispatcherTimer object.
  125. /// </summary>
  126. /// <typeparam name="TState">The type of the state passed to the scheduled action.</typeparam>
  127. /// <param name="state">State passed to the action to be executed.</param>
  128. /// <param name="action">Action to be executed.</param>
  129. /// <param name="dueTime">Relative time after which to execute the action.</param>
  130. /// <returns>The disposable object used to cancel the scheduled action (best effort).</returns>
  131. /// <exception cref="ArgumentNullException"><paramref name="action"/> is null.</exception>
  132. public override IDisposable Schedule<TState>(TState state, TimeSpan dueTime, Func<IScheduler, TState, IDisposable> action)
  133. {
  134. if (action == null)
  135. throw new ArgumentNullException("action");
  136. var dt = Scheduler.Normalize(dueTime);
  137. if (dt.Ticks == 0)
  138. return Schedule(state, action);
  139. var d = new MultipleAssignmentDisposable();
  140. var timer = new DispatcherTimer();
  141. timer.Tick += (o, e) =>
  142. {
  143. var t = Interlocked.Exchange(ref timer, null);
  144. if (t != null)
  145. {
  146. try
  147. {
  148. d.Disposable = action(this, state);
  149. }
  150. finally
  151. {
  152. t.Stop();
  153. action = null;
  154. }
  155. }
  156. };
  157. timer.Interval = dt;
  158. timer.Start();
  159. d.Disposable = Disposable.Create(() =>
  160. {
  161. var t = Interlocked.Exchange(ref timer, null);
  162. if (t != null)
  163. {
  164. t.Stop();
  165. action = (_, __) => Disposable.Empty;
  166. }
  167. });
  168. return d;
  169. }
  170. /// <summary>
  171. /// Schedules a periodic piece of work on the dispatcher, using a Windows.UI.Xaml.DispatcherTimer object.
  172. /// </summary>
  173. /// <typeparam name="TState">The type of the state passed to the scheduled action.</typeparam>
  174. /// <param name="state">Initial state passed to the action upon the first iteration.</param>
  175. /// <param name="period">Period for running the work periodically.</param>
  176. /// <param name="action">Action to be executed, potentially updating the state.</param>
  177. /// <returns>The disposable object used to cancel the scheduled recurring action (best effort).</returns>
  178. /// <exception cref="ArgumentNullException"><paramref name="action"/> is null.</exception>
  179. /// <exception cref="ArgumentOutOfRangeException"><paramref name="period"/> is less than TimeSpan.Zero.</exception>
  180. public IDisposable SchedulePeriodic<TState>(TState state, TimeSpan period, Func<TState, TState> action)
  181. {
  182. //
  183. // According to MSDN documentation, the default is TimeSpan.Zero, so that's definitely valid.
  184. // Empirical observation - negative values seem to be normalized to TimeSpan.Zero, but let's not go there.
  185. //
  186. if (period < TimeSpan.Zero)
  187. throw new ArgumentOutOfRangeException("period");
  188. if (action == null)
  189. throw new ArgumentNullException("action");
  190. var timer = new DispatcherTimer();
  191. var state1 = state;
  192. timer.Tick += (o, e) =>
  193. {
  194. state1 = action(state1);
  195. };
  196. timer.Interval = period;
  197. timer.Start();
  198. return Disposable.Create(() =>
  199. {
  200. var t = Interlocked.Exchange(ref timer, null);
  201. if (t != null)
  202. {
  203. t.Stop();
  204. action = _ => _;
  205. }
  206. });
  207. }
  208. }
  209. }
  210. #endif