ControlScheduler.cs 7.7 KB

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