234 lines
7.4 KiB
C#
234 lines
7.4 KiB
C#
#nullable enable
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace Swan.Threading {
|
|
/// <summary>
|
|
/// Provides base infrastructure for Timer and Thread workers.
|
|
/// </summary>
|
|
/// <seealso cref="IWorker" />
|
|
public abstract class WorkerBase : IWorker, IDisposable {
|
|
// Since these are API property backers, we use interlocked to read from them
|
|
// to avoid deadlocked reads
|
|
private readonly Object _syncLock = new Object();
|
|
|
|
private readonly AtomicBoolean _isDisposed = new AtomicBoolean();
|
|
private readonly AtomicBoolean _isDisposing = new AtomicBoolean();
|
|
private readonly AtomicEnum<WorkerState> _workerState = new AtomicEnum<WorkerState>(WorkerState.Created);
|
|
private readonly AtomicTimeSpan _timeSpan;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="WorkerBase"/> class.
|
|
/// </summary>
|
|
/// <param name="name">The name.</param>
|
|
/// <param name="period">The execution interval.</param>
|
|
protected WorkerBase(String name, TimeSpan period) {
|
|
this.Name = name;
|
|
this._timeSpan = new AtomicTimeSpan(period);
|
|
|
|
this.StateChangeRequests = new Dictionary<StateChangeRequest, Boolean>(5) {
|
|
[StateChangeRequest.Start] = false,
|
|
[StateChangeRequest.Pause] = false,
|
|
[StateChangeRequest.Resume] = false,
|
|
[StateChangeRequest.Stop] = false,
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enumerates all the different state change requests.
|
|
/// </summary>
|
|
protected enum StateChangeRequest {
|
|
/// <summary>
|
|
/// No state change request.
|
|
/// </summary>
|
|
None,
|
|
|
|
/// <summary>
|
|
/// Start state change request
|
|
/// </summary>
|
|
Start,
|
|
|
|
/// <summary>
|
|
/// Pause state change request
|
|
/// </summary>
|
|
Pause,
|
|
|
|
/// <summary>
|
|
/// Resume state change request
|
|
/// </summary>
|
|
Resume,
|
|
|
|
/// <summary>
|
|
/// Stop state change request
|
|
/// </summary>
|
|
Stop,
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public String Name {
|
|
get;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public TimeSpan Period {
|
|
get => this._timeSpan.Value;
|
|
set => this._timeSpan.Value = value;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public WorkerState WorkerState {
|
|
get => this._workerState.Value;
|
|
protected set => this._workerState.Value = value;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Boolean IsDisposed {
|
|
get => this._isDisposed.Value;
|
|
protected set => this._isDisposed.Value = value;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Boolean IsDisposing {
|
|
get => this._isDisposing.Value;
|
|
protected set => this._isDisposing.Value = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the default period of 15 milliseconds which is the default precision for timers.
|
|
/// </summary>
|
|
protected static TimeSpan DefaultPeriod { get; } = TimeSpan.FromMilliseconds(15);
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether stop has been requested.
|
|
/// This is useful to prevent more requests from being issued.
|
|
/// </summary>
|
|
protected Boolean IsStopRequested => this.StateChangeRequests[StateChangeRequest.Stop];
|
|
|
|
/// <summary>
|
|
/// Gets the cycle stopwatch.
|
|
/// </summary>
|
|
protected Stopwatch CycleStopwatch { get; } = new Stopwatch();
|
|
|
|
/// <summary>
|
|
/// Gets the state change requests.
|
|
/// </summary>
|
|
protected Dictionary<StateChangeRequest, Boolean> StateChangeRequests {
|
|
get;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the cycle completed event.
|
|
/// </summary>
|
|
protected ManualResetEventSlim CycleCompletedEvent { get; } = new ManualResetEventSlim(true);
|
|
|
|
/// <summary>
|
|
/// Gets the state changed event.
|
|
/// </summary>
|
|
protected ManualResetEventSlim StateChangedEvent { get; } = new ManualResetEventSlim(true);
|
|
|
|
/// <summary>
|
|
/// Gets the cycle logic cancellation owner.
|
|
/// </summary>
|
|
protected CancellationTokenOwner CycleCancellation { get; } = new CancellationTokenOwner();
|
|
|
|
/// <summary>
|
|
/// Gets or sets the state change task.
|
|
/// </summary>
|
|
protected Task<WorkerState>? StateChangeTask {
|
|
get; set;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public abstract Task<WorkerState> StartAsync();
|
|
|
|
/// <inheritdoc />
|
|
public abstract Task<WorkerState> PauseAsync();
|
|
|
|
/// <inheritdoc />
|
|
public abstract Task<WorkerState> ResumeAsync();
|
|
|
|
/// <inheritdoc />
|
|
public abstract Task<WorkerState> StopAsync();
|
|
|
|
/// <inheritdoc />
|
|
public void Dispose() {
|
|
this.Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Releases unmanaged and - optionally - managed resources.
|
|
/// </summary>
|
|
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
|
protected virtual void Dispose(Boolean disposing) {
|
|
lock(this._syncLock) {
|
|
if(this.IsDisposed || this.IsDisposing) {
|
|
return;
|
|
}
|
|
|
|
this.IsDisposing = true;
|
|
}
|
|
|
|
// This also ensures the state change queue gets cleared
|
|
this.StopAsync().Wait();
|
|
this.StateChangedEvent.Set();
|
|
this.CycleCompletedEvent.Set();
|
|
|
|
this.OnDisposing();
|
|
|
|
this.CycleStopwatch.Stop();
|
|
this.StateChangedEvent.Dispose();
|
|
this.CycleCompletedEvent.Dispose();
|
|
this.CycleCancellation.Dispose();
|
|
|
|
this.IsDisposed = true;
|
|
this.IsDisposing = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles the cycle logic exceptions.
|
|
/// </summary>
|
|
/// <param name="ex">The exception that was thrown.</param>
|
|
protected abstract void OnCycleException(Exception ex);
|
|
|
|
/// <summary>
|
|
/// Represents the user defined logic to be executed on a single worker cycle.
|
|
/// Check the cancellation token continuously if you need responsive interrupts.
|
|
/// </summary>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
protected abstract void ExecuteCycleLogic(CancellationToken cancellationToken);
|
|
|
|
/// <summary>
|
|
/// This method is called automatically when <see cref="Dispose()"/> is called.
|
|
/// Makes sure you release all resources within this call.
|
|
/// </summary>
|
|
protected abstract void OnDisposing();
|
|
|
|
/// <summary>
|
|
/// Called when a state change request is processed.
|
|
/// </summary>
|
|
/// <param name="previousState">The state before the change.</param>
|
|
/// <param name="newState">The new state.</param>
|
|
protected virtual void OnStateChangeProcessed(WorkerState previousState, WorkerState newState) {
|
|
// placeholder
|
|
}
|
|
|
|
/// <summary>
|
|
/// Computes the cycle delay.
|
|
/// </summary>
|
|
/// <param name="initialWorkerState">Initial state of the worker.</param>
|
|
/// <returns>The number of milliseconds to delay for.</returns>
|
|
protected Int32 ComputeCycleDelay(WorkerState initialWorkerState) {
|
|
Int64 elapsedMillis = this.CycleStopwatch.ElapsedMilliseconds;
|
|
TimeSpan period = this.Period;
|
|
Double periodMillis = period.TotalMilliseconds;
|
|
Double delayMillis = periodMillis - elapsedMillis;
|
|
|
|
return initialWorkerState == WorkerState.Paused || period == TimeSpan.MaxValue || delayMillis >= Int32.MaxValue ? Timeout.Infinite : elapsedMillis >= periodMillis ? 0 : Convert.ToInt32(Math.Floor(delayMillis));
|
|
}
|
|
}
|
|
}
|