// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System.Reactive.Disposables; using System.Threading; using System.Windows.Forms; namespace System.Reactive.Concurrency { /// /// Represents an object that schedules units of work on the message loop associated with a Windows Forms control. /// public class ControlScheduler : LocalScheduler, ISchedulerPeriodic { private readonly Control _control; /// /// Constructs a ControlScheduler that schedules units of work on the message loop associated with the specified Windows Forms control. /// /// Windows Forms control to get the message loop from. /// is null. /// /// This scheduler type is typically used indirectly through the and method overloads that take a Windows Forms control. /// public ControlScheduler(Control control) { if (control == null) throw new ArgumentNullException("control"); _control = control; } /// /// Gets the control associated with the ControlScheduler. /// public Control Control { get { return _control; } } /// /// Schedules an action to be executed on the message loop associated with the control. /// /// 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("action"); var d = new SingleAssignmentDisposable(); _control.BeginInvoke(new Action(() => { if (!d.IsDisposed) d.Disposable = action(this, state); })); return d; } /// /// Schedules an action to be executed after dueTime on the message loop associated with the control, using a Windows Forms Timer 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("action"); var dt = Scheduler.Normalize(dueTime); if (dt.Ticks == 0) return Schedule(state, action); var createTimer = new Func((scheduler1, state1) => { var d = new MultipleAssignmentDisposable(); var timer = new System.Windows.Forms.Timer(); timer.Tick += (s, e) => { var t = Interlocked.Exchange(ref timer, null); if (t != null) { try { d.Disposable = action(scheduler1, state1); } finally { t.Stop(); action = null; } } }; timer.Interval = (int)dt.TotalMilliseconds; timer.Start(); d.Disposable = Disposable.Create(() => { var t = Interlocked.Exchange(ref timer, null); if (t != null) { t.Stop(); action = (_, __) => Disposable.Empty; } }); return d; }); // // This check is critical. When creating and enabling a Timer object on another thread than // the UI thread, it won't fire. // if (_control.InvokeRequired) return Schedule(state, createTimer); else return createTimer(this, state); } /// /// Schedules a periodic piece of work on the message loop associated with the control, using a Windows Forms Timer 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) { // // Threshold derived from Interval property setter in ndp\fx\src\winforms\managed\system\winforms\Timer.cs. // if (period.TotalMilliseconds < 1) throw new ArgumentOutOfRangeException("period"); if (action == null) throw new ArgumentNullException("action"); var createTimer = new Func((scheduler1, state1) => { var timer = new System.Windows.Forms.Timer(); timer.Tick += (s, e) => { state1 = action(state1); }; timer.Interval = (int)period.TotalMilliseconds; timer.Start(); return Disposable.Create(() => { var t = Interlocked.Exchange(ref timer, null); if (t != null) { t.Stop(); action = _ => _; } }); }); // // This check is critical. When creating and enabling a Timer object on another thread than // the UI thread, it won't fire. // if (_control.InvokeRequired) return Schedule(state, createTimer); else return createTimer(this, state); } } }