// 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);
}
}
}