using System; using System.Threading; namespace Swan.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 Int32 _period; /// /// Initializes a new instance of the class. /// /// The timer callback. /// The state. /// The due time. /// The period. public ExclusiveTimer(TimerCallback timerCallback, Object state, Int32 dueTime, Int32 period) { this._period = period; this._userCallback = timerCallback; this._backingTimer = new Timer(this.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) { // placeholder } /// /// Initializes a new instance of the class. /// /// The timer callback. /// The due time. /// The period. public ExclusiveTimer(Action timerCallback, Int32 dueTime, Int32 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 Boolean IsDisposing => this._isDisposing.Value; /// /// Gets a value indicating whether this instance is disposed. /// /// /// true if this instance is disposed; otherwise, false. /// public Boolean IsDisposed => this._isDisposed.Value; /// /// Waits until the time is elapsed. /// /// The until date. /// The cancellation token. public static void WaitUntil(DateTime untilDate, CancellationToken cancellationToken = default) { static void Callback(IWaitEvent waitEvent) { try { waitEvent.Complete(); waitEvent.Begin(); } catch { // ignore } } using IWaitEvent delayLock = WaitEventFactory.Create(true); using ExclusiveTimer _ = new ExclusiveTimer(() => Callback(delayLock), 0, 15); while(!cancellationToken.IsCancellationRequested && DateTime.UtcNow < untilDate) { delayLock.Wait(); } } /// /// Waits the specified wait time. /// /// The wait time. /// The cancellation token. public static void Wait(TimeSpan waitTime, CancellationToken cancellationToken = default) => WaitUntil(DateTime.UtcNow.Add(waitTime), cancellationToken); /// /// Changes the start time and the interval between method invocations for the internal timer. /// /// The due time. /// The period. public void Change(Int32 dueTime, Int32 period) { this._period = period; _ = this._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) => this.Change(Convert.ToInt32(dueTime.TotalMilliseconds), Convert.ToInt32(period.TotalMilliseconds)); /// /// Changes the interval between method invocations for the internal timer. /// /// The period. public void Resume(Int32 period) => this.Change(0, period); /// /// Changes the interval between method invocations for the internal timer. /// /// The period. public void Resume(TimeSpan period) => this.Change(TimeSpan.Zero, period); /// /// Pauses this instance. /// public void Pause() => this.Change(Timeout.Infinite, Timeout.Infinite); /// public void Dispose() { lock(this._syncLock) { if(this._isDisposed == true || this._isDisposing == true) { return; } this._isDisposing.Value = true; } try { this._cycleDoneEvent.Wait(); this._cycleDoneEvent.Dispose(); this.Pause(); this._backingTimer.Dispose(); } finally { this._isDisposed.Value = true; this._isDisposing.Value = false; } } /// /// Logic that runs every time the timer hits the due time. /// /// The state. private void InternalCallback(Object state) { lock(this._syncLock) { if(this.IsDisposed || this.IsDisposing) { return; } } if(this._cycleDoneEvent.IsSet == false) { return; } this._cycleDoneEvent.Reset(); try { this._userCallback(state); } finally { this._cycleDoneEvent?.Set(); _ = this._backingTimer?.Change(this._period, Timeout.Infinite); } } } }