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