ControlScheduler.cs 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the Apache 2.0 License.
  3. // See the LICENSE file in the project root for more information.
  4. using System.Reactive.Disposables;
  5. using System.Threading;
  6. using System.Windows.Forms;
  7. namespace System.Reactive.Concurrency
  8. {
  9. /// <summary>
  10. /// Represents an object that schedules units of work on the message loop associated with a Windows Forms control.
  11. /// </summary>
  12. public class ControlScheduler : LocalScheduler, ISchedulerPeriodic
  13. {
  14. private readonly Control _control;
  15. /// <summary>
  16. /// Constructs a ControlScheduler that schedules units of work on the message loop associated with the specified Windows Forms control.
  17. /// </summary>
  18. /// <param name="control">Windows Forms control to get the message loop from.</param>
  19. /// <exception cref="ArgumentNullException"><paramref name="control"/> is null.</exception>
  20. /// <remarks>
  21. /// This scheduler type is typically used indirectly through the <see cref="System.Reactive.Linq.ControlObservable.ObserveOn&lt;TSource&gt;"/> and <see cref="System.Reactive.Linq.ControlObservable.SubscribeOn&lt;TSource&gt;"/> method overloads that take a Windows Forms control.
  22. /// </remarks>
  23. public ControlScheduler(Control control)
  24. {
  25. if (control == null)
  26. throw new ArgumentNullException(nameof(control));
  27. _control = control;
  28. }
  29. /// <summary>
  30. /// Gets the control associated with the ControlScheduler.
  31. /// </summary>
  32. public Control Control
  33. {
  34. get { return _control; }
  35. }
  36. /// <summary>
  37. /// Schedules an action to be executed on the message loop associated with the control.
  38. /// </summary>
  39. /// <typeparam name="TState">The type of the state passed to the scheduled action.</typeparam>
  40. /// <param name="state">State passed to the action to be executed.</param>
  41. /// <param name="action">Action to be executed.</param>
  42. /// <returns>The disposable object used to cancel the scheduled action (best effort).</returns>
  43. /// <exception cref="ArgumentNullException"><paramref name="action"/> is null.</exception>
  44. public override IDisposable Schedule<TState>(TState state, Func<IScheduler, TState, IDisposable> action)
  45. {
  46. if (action == null)
  47. throw new ArgumentNullException(nameof(action));
  48. var d = new SingleAssignmentDisposable();
  49. _control.BeginInvoke(new Action(() =>
  50. {
  51. if (!d.IsDisposed)
  52. d.Disposable = action(this, state);
  53. }));
  54. return d;
  55. }
  56. /// <summary>
  57. /// Schedules an action to be executed after dueTime on the message loop associated with the control, using a Windows Forms Timer object.
  58. /// </summary>
  59. /// <typeparam name="TState">The type of the state passed to the scheduled action.</typeparam>
  60. /// <param name="state">State passed to the action to be executed.</param>
  61. /// <param name="action">Action to be executed.</param>
  62. /// <param name="dueTime">Relative time after which to execute the action.</param>
  63. /// <returns>The disposable object used to cancel the scheduled action (best effort).</returns>
  64. /// <exception cref="ArgumentNullException"><paramref name="action"/> is null.</exception>
  65. public override IDisposable Schedule<TState>(TState state, TimeSpan dueTime, Func<IScheduler, TState, IDisposable> action)
  66. {
  67. if (action == null)
  68. throw new ArgumentNullException(nameof(action));
  69. var dt = Scheduler.Normalize(dueTime);
  70. if (dt.Ticks == 0)
  71. return Schedule(state, action);
  72. var createTimer = new Func<IScheduler, TState, IDisposable>((scheduler1, state1) =>
  73. {
  74. var d = new MultipleAssignmentDisposable();
  75. var timer = new System.Windows.Forms.Timer();
  76. timer.Tick += (s, e) =>
  77. {
  78. var t = Interlocked.Exchange(ref timer, null);
  79. if (t != null)
  80. {
  81. try
  82. {
  83. d.Disposable = action(scheduler1, state1);
  84. }
  85. finally
  86. {
  87. t.Stop();
  88. action = null;
  89. }
  90. }
  91. };
  92. timer.Interval = (int)dt.TotalMilliseconds;
  93. timer.Start();
  94. d.Disposable = Disposable.Create(() =>
  95. {
  96. var t = Interlocked.Exchange(ref timer, null);
  97. if (t != null)
  98. {
  99. t.Stop();
  100. action = (_, __) => Disposable.Empty;
  101. }
  102. });
  103. return d;
  104. });
  105. //
  106. // This check is critical. When creating and enabling a Timer object on another thread than
  107. // the UI thread, it won't fire.
  108. //
  109. if (_control.InvokeRequired)
  110. return Schedule(state, createTimer);
  111. else
  112. return createTimer(this, state);
  113. }
  114. /// <summary>
  115. /// Schedules a periodic piece of work on the message loop associated with the control, using a Windows Forms Timer object.
  116. /// </summary>
  117. /// <typeparam name="TState">The type of the state passed to the scheduled action.</typeparam>
  118. /// <param name="state">Initial state passed to the action upon the first iteration.</param>
  119. /// <param name="period">Period for running the work periodically.</param>
  120. /// <param name="action">Action to be executed, potentially updating the state.</param>
  121. /// <returns>The disposable object used to cancel the scheduled recurring action (best effort).</returns>
  122. /// <exception cref="ArgumentNullException"><paramref name="action"/> is null.</exception>
  123. /// <exception cref="ArgumentOutOfRangeException"><paramref name="period"/> is less than one millisecond.</exception>
  124. public IDisposable SchedulePeriodic<TState>(TState state, TimeSpan period, Func<TState, TState> action)
  125. {
  126. //
  127. // Threshold derived from Interval property setter in ndp\fx\src\winforms\managed\system\winforms\Timer.cs.
  128. //
  129. if (period.TotalMilliseconds < 1)
  130. throw new ArgumentOutOfRangeException(nameof(period));
  131. if (action == null)
  132. throw new ArgumentNullException(nameof(action));
  133. var createTimer = new Func<IScheduler, TState, IDisposable>((scheduler1, state1) =>
  134. {
  135. var timer = new System.Windows.Forms.Timer();
  136. timer.Tick += (s, e) =>
  137. {
  138. state1 = action(state1);
  139. };
  140. timer.Interval = (int)period.TotalMilliseconds;
  141. timer.Start();
  142. return Disposable.Create(() =>
  143. {
  144. var t = Interlocked.Exchange(ref timer, null);
  145. if (t != null)
  146. {
  147. t.Stop();
  148. action = _ => _;
  149. }
  150. });
  151. });
  152. //
  153. // This check is critical. When creating and enabling a Timer object on another thread than
  154. // the UI thread, it won't fire.
  155. //
  156. if (_control.InvokeRequired)
  157. return Schedule(state, createTimer);
  158. else
  159. return createTimer(this, state);
  160. }
  161. }
  162. }