namespace Unosquare.Swan.Abstractions
{
using System;
using System.Threading;
///
/// A threading implementation that executes at most one cycle at a time
/// in a thread. Callback execution is NOT guaranteed to be carried out
/// on the same thread every time the timer fires.
///
public sealed class ExclusiveTimer : IDisposable
{
private readonly object _syncLock = new object();
private readonly ManualResetEventSlim _cycleDoneEvent = new ManualResetEventSlim(true);
private readonly Timer _backingTimer;
private readonly TimerCallback _userCallback;
private readonly AtomicBoolean _isDisposing = new AtomicBoolean();
private readonly AtomicBoolean _isDisposed = new AtomicBoolean();
private int _period;
///
/// Initializes a new instance of the class.
///
/// The timer callback.
/// The state.
/// The due time.
/// The period.
public ExclusiveTimer(TimerCallback timerCallback, object state, int dueTime, int period)
{
_period = period;
_userCallback = timerCallback;
_backingTimer = new Timer(InternalCallback, state ?? this, dueTime, Timeout.Infinite);
}
///
/// Initializes a new instance of the class.
///
/// The timer callback.
/// The state.
/// The due time.
/// The period.
public ExclusiveTimer(TimerCallback timerCallback, object state, TimeSpan dueTime, TimeSpan period)
: this(timerCallback, state, Convert.ToInt32(dueTime.TotalMilliseconds), Convert.ToInt32(period.TotalMilliseconds))
{
// placeholder
}
///
/// Initializes a new instance of the class.
///
/// The timer callback.
public ExclusiveTimer(TimerCallback timerCallback)
: this(timerCallback, null, Timeout.Infinite, Timeout.Infinite)
{
// placholder
}
///
/// Initializes a new instance of the class.
///
/// The timer callback.
/// The due time.
/// The period.
public ExclusiveTimer(Action timerCallback, int dueTime, int period)
: this(s => { timerCallback?.Invoke(); }, null, dueTime, period)
{
// placeholder
}
///
/// Initializes a new instance of the class.
///
/// The timer callback.
/// The due time.
/// The period.
public ExclusiveTimer(Action timerCallback, TimeSpan dueTime, TimeSpan period)
: this(s => { timerCallback?.Invoke(); }, null, dueTime, period)
{
// placeholder
}
///
/// Initializes a new instance of the class.
///
/// The timer callback.
public ExclusiveTimer(Action timerCallback)
: this(timerCallback, Timeout.Infinite, Timeout.Infinite)
{
// placeholder
}
///
/// Gets a value indicating whether this instance is disposing.
///
///
/// true if this instance is disposing; otherwise, false.
///
public bool IsDisposing => _isDisposing.Value;
///
/// Gets a value indicating whether this instance is disposed.
///
///
/// true if this instance is disposed; otherwise, false.
///
public bool IsDisposed => _isDisposed.Value;
///
/// Changes the start time and the interval between method invocations for the internal timer.
///
/// The due time.
/// The period.
public void Change(int dueTime, int period)
{
_period = period;
_backingTimer.Change(dueTime, Timeout.Infinite);
}
///
/// Changes the start time and the interval between method invocations for the internal timer.
///
/// The due time.
/// The period.
public void Change(TimeSpan dueTime, TimeSpan period)
=> Change(Convert.ToInt32(dueTime.TotalMilliseconds), Convert.ToInt32(period.TotalMilliseconds));
///
/// Changes the interval between method invocations for the internal timer.
///
/// The period.
public void Resume(int period) => Change(0, period);
///
/// Changes the interval between method invocations for the internal timer.
///
/// The period.
public void Resume(TimeSpan period) => Change(TimeSpan.Zero, period);
///
/// Pauses this instance.
///
public void Pause() => Change(Timeout.Infinite, Timeout.Infinite);
///
public void Dispose()
{
lock (_syncLock)
{
if (_isDisposed == true || _isDisposing == true)
return;
_isDisposing.Value = true;
}
try
{
_backingTimer.Dispose();
_cycleDoneEvent.Wait();
_cycleDoneEvent.Dispose();
}
finally
{
_isDisposed.Value = true;
_isDisposing.Value = false;
}
}
///
/// Logic that runs every time the timer hits the due time.
///
/// The state.
private void InternalCallback(object state)
{
lock (_syncLock)
{
if (IsDisposed || IsDisposing)
return;
}
if (_cycleDoneEvent.IsSet == false)
return;
_cycleDoneEvent.Reset();
try
{
_userCallback(state);
}
finally
{
_cycleDoneEvent?.Set();
_backingTimer?.Change(_period, Timeout.Infinite);
}
}
}
}