RaspberryIO_26/Swan/Threading/WorkerBase.cs

234 lines
7.4 KiB
C#
Raw Normal View History

2019-12-08 21:23:54 +01:00
#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));
}
}
2019-12-04 18:57:18 +01:00
}