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