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