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 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) { // placeholder } /// /// 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; /// /// Waits until the time is elapsed. /// /// The until date. /// The cancellation token. public static void WaitUntil(DateTime untilDate, CancellationToken cancellationToken = default) { void Callback(IWaitEvent waitEvent) { try { waitEvent.Complete(); waitEvent.Begin(); } catch { // ignore } } using (var delayLock = WaitEventFactory.Create(true)) { using (var _ = 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(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 { _cycleDoneEvent.Wait(); _cycleDoneEvent.Dispose(); Pause(); _backingTimer.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); } } } }