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