#nullable enable using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; namespace Swan.Threading { /// /// 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) { this.Name = name; this._timeSpan = new AtomicTimeSpan(period); this.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 => this._timeSpan.Value; set => this._timeSpan.Value = value; } /// public WorkerState WorkerState { get => this._workerState.Value; protected set => this._workerState.Value = value; } /// public Boolean IsDisposed { get => this._isDisposed.Value; protected set => this._isDisposed.Value = value; } /// public Boolean IsDisposing { get => this._isDisposing.Value; protected set => this._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 Boolean IsStopRequested => this.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() { this.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(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; } /// /// 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 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)); } } }