182 lines
6.6 KiB
C#
182 lines
6.6 KiB
C#
using System;
|
|
using System.Threading;
|
|
|
|
namespace Unosquare.Swan.Abstractions {
|
|
/// <summary>
|
|
/// A threading <see cref="_backingTimer"/> implementation that executes at most one cycle at a time
|
|
/// in a <see cref="ThreadPool"/> thread. Callback execution is NOT guaranteed to be carried out
|
|
/// on the same <see cref="ThreadPool"/> thread every time the timer fires.
|
|
/// </summary>
|
|
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;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
|
|
/// </summary>
|
|
/// <param name="timerCallback">The timer callback.</param>
|
|
/// <param name="state">The state.</param>
|
|
/// <param name="dueTime">The due time.</param>
|
|
/// <param name="period">The period.</param>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
|
|
/// </summary>
|
|
/// <param name="timerCallback">The timer callback.</param>
|
|
/// <param name="state">The state.</param>
|
|
/// <param name="dueTime">The due time.</param>
|
|
/// <param name="period">The period.</param>
|
|
public ExclusiveTimer(TimerCallback timerCallback, Object state, TimeSpan dueTime, TimeSpan period)
|
|
: this(timerCallback, state, Convert.ToInt32(dueTime.TotalMilliseconds), Convert.ToInt32(period.TotalMilliseconds)) {
|
|
// placeholder
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
|
|
/// </summary>
|
|
/// <param name="timerCallback">The timer callback.</param>
|
|
public ExclusiveTimer(TimerCallback timerCallback)
|
|
: this(timerCallback, null, Timeout.Infinite, Timeout.Infinite) {
|
|
// placholder
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
|
|
/// </summary>
|
|
/// <param name="timerCallback">The timer callback.</param>
|
|
/// <param name="dueTime">The due time.</param>
|
|
/// <param name="period">The period.</param>
|
|
public ExclusiveTimer(Action timerCallback, Int32 dueTime, Int32 period)
|
|
: this(s => timerCallback?.Invoke(), null, dueTime, period) {
|
|
// placeholder
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
|
|
/// </summary>
|
|
/// <param name="timerCallback">The timer callback.</param>
|
|
/// <param name="dueTime">The due time.</param>
|
|
/// <param name="period">The period.</param>
|
|
public ExclusiveTimer(Action timerCallback, TimeSpan dueTime, TimeSpan period)
|
|
: this(s => timerCallback?.Invoke(), null, dueTime, period) {
|
|
// placeholder
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
|
|
/// </summary>
|
|
/// <param name="timerCallback">The timer callback.</param>
|
|
public ExclusiveTimer(Action timerCallback)
|
|
: this(timerCallback, Timeout.Infinite, Timeout.Infinite) {
|
|
// placeholder
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether this instance is disposing.
|
|
/// </summary>
|
|
/// <value>
|
|
/// <c>true</c> if this instance is disposing; otherwise, <c>false</c>.
|
|
/// </value>
|
|
public Boolean IsDisposing => this._isDisposing.Value;
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether this instance is disposed.
|
|
/// </summary>
|
|
/// <value>
|
|
/// <c>true</c> if this instance is disposed; otherwise, <c>false</c>.
|
|
/// </value>
|
|
public Boolean IsDisposed => this._isDisposed.Value;
|
|
|
|
/// <summary>
|
|
/// Changes the start time and the interval between method invocations for the internal timer.
|
|
/// </summary>
|
|
/// <param name="dueTime">The due time.</param>
|
|
/// <param name="period">The period.</param>
|
|
public void Change(Int32 dueTime, Int32 period) {
|
|
this._period = period;
|
|
|
|
_ = this._backingTimer.Change(dueTime, Timeout.Infinite);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Changes the start time and the interval between method invocations for the internal timer.
|
|
/// </summary>
|
|
/// <param name="dueTime">The due time.</param>
|
|
/// <param name="period">The period.</param>
|
|
public void Change(TimeSpan dueTime, TimeSpan period)
|
|
=> this.Change(Convert.ToInt32(dueTime.TotalMilliseconds), Convert.ToInt32(period.TotalMilliseconds));
|
|
|
|
/// <summary>
|
|
/// Changes the interval between method invocations for the internal timer.
|
|
/// </summary>
|
|
/// <param name="period">The period.</param>
|
|
public void Resume(Int32 period) => this.Change(0, period);
|
|
|
|
/// <summary>
|
|
/// Changes the interval between method invocations for the internal timer.
|
|
/// </summary>
|
|
/// <param name="period">The period.</param>
|
|
public void Resume(TimeSpan period) => this.Change(TimeSpan.Zero, period);
|
|
|
|
/// <summary>
|
|
/// Pauses this instance.
|
|
/// </summary>
|
|
public void Pause() => this.Change(Timeout.Infinite, Timeout.Infinite);
|
|
|
|
/// <inheritdoc />
|
|
public void Dispose() {
|
|
lock(this._syncLock) {
|
|
if(this._isDisposed == true || this._isDisposing == true) {
|
|
return;
|
|
}
|
|
|
|
this._isDisposing.Value = true;
|
|
}
|
|
|
|
try {
|
|
this._backingTimer.Dispose();
|
|
this._cycleDoneEvent.Wait();
|
|
this._cycleDoneEvent.Dispose();
|
|
} finally {
|
|
this._isDisposed.Value = true;
|
|
this._isDisposing.Value = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Logic that runs every time the timer hits the due time.
|
|
/// </summary>
|
|
/// <param name="state">The state.</param>
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|