namespace Swan.Threading { using System; using System.Threading; using System.Threading.Tasks; /// /// Provides a base implementation for application workers /// that perform continuous, long-running tasks. This class /// provides the ability to perform fine-grained control on these tasks. /// /// public abstract class ThreadWorkerBase : WorkerBase { private readonly Object _syncLock = new Object(); private readonly Thread _thread; /// /// Initializes a new instance of the class. /// /// The name. /// The thread priority. /// The interval of cycle execution. /// The cycle delay provide implementation. protected ThreadWorkerBase(String name, ThreadPriority priority, TimeSpan period, IWorkerDelayProvider delayProvider) : base(name, period) { this.DelayProvider = delayProvider; this._thread = new Thread(this.RunWorkerLoop) { IsBackground = true, Priority = priority, Name = name, }; } /// /// Initializes a new instance of the class. /// /// The name. /// The execution interval. protected ThreadWorkerBase(String name, TimeSpan period) : this(name, ThreadPriority.Normal, period, WorkerDelayProvider.Default) { // placeholder } /// /// Provides an implementation on a cycle delay provider. /// protected IWorkerDelayProvider DelayProvider { get; } /// public override Task StartAsync() { lock(this._syncLock) { if(this.WorkerState == WorkerState.Paused || this.WorkerState == WorkerState.Waiting) { return this.ResumeAsync(); } if(this.WorkerState != WorkerState.Created) { return Task.FromResult(this.WorkerState); } if(this.IsStopRequested) { return Task.FromResult(this.WorkerState); } Task task = this.QueueStateChange(StateChangeRequest.Start); this._thread.Start(); return task; } } /// public override Task PauseAsync() { lock(this._syncLock) { return this.WorkerState != WorkerState.Running && this.WorkerState != WorkerState.Waiting ? Task.FromResult(this.WorkerState) : this.IsStopRequested ? Task.FromResult(this.WorkerState) : this.QueueStateChange(StateChangeRequest.Pause); } } /// public override Task ResumeAsync() { lock(this._syncLock) { return this.WorkerState == WorkerState.Created ? this.StartAsync() : this.WorkerState != WorkerState.Paused && this.WorkerState != WorkerState.Waiting ? Task.FromResult(this.WorkerState) : this.IsStopRequested ? Task.FromResult(this.WorkerState) : this.QueueStateChange(StateChangeRequest.Resume); } } /// public override Task StopAsync() { lock(this._syncLock) { if(this.WorkerState == WorkerState.Stopped || this.WorkerState == WorkerState.Created) { this.WorkerState = WorkerState.Stopped; return Task.FromResult(this.WorkerState); } return this.QueueStateChange(StateChangeRequest.Stop); } } /// /// Suspends execution queues a new new cycle for execution. The delay is given in /// milliseconds. When overridden in a derived class the wait handle will be set /// whenever an interrupt is received. /// /// The remaining delay to wait for in the cycle. /// Contains a reference to a task with the scheduled period delay. /// The cancellation token to cancel waiting. protected virtual void ExecuteCycleDelay(Int32 wantedDelay, Task delayTask, CancellationToken token) => this.DelayProvider?.ExecuteCycleDelay(wantedDelay, delayTask, token); /// protected override void OnDisposing() { lock(this._syncLock) { if((this._thread.ThreadState & ThreadState.Unstarted) != ThreadState.Unstarted) { this._thread.Join(); } } } /// /// Implements worker control, execution and delay logic in a loop. /// private void RunWorkerLoop() { while(this.WorkerState != WorkerState.Stopped && !this.IsDisposing && !this.IsDisposed) { this.CycleStopwatch.Restart(); CancellationToken interruptToken = this.CycleCancellation.Token; Int32 period = this.Period.TotalMilliseconds >= Int32.MaxValue ? -1 : Convert.ToInt32(Math.Floor(this.Period.TotalMilliseconds)); Task delayTask = Task.Delay(period, interruptToken); WorkerState initialWorkerState = this.WorkerState; // Lock the cycle and capture relevant state valid for this cycle this.CycleCompletedEvent.Reset(); // Process the tasks that are awaiting if(this.ProcessStateChangeRequests()) { continue; } try { if(initialWorkerState == WorkerState.Waiting && !interruptToken.IsCancellationRequested) { // Mark the state as Running this.WorkerState = WorkerState.Running; // Call the execution logic this.ExecuteCycleLogic(interruptToken); } } catch(Exception ex) { this.OnCycleException(ex); } finally { // Update the state this.WorkerState = initialWorkerState == WorkerState.Paused ? WorkerState.Paused : WorkerState.Waiting; // Signal the cycle has been completed so new cycles can be executed this.CycleCompletedEvent.Set(); if(!interruptToken.IsCancellationRequested) { Int32 cycleDelay = this.ComputeCycleDelay(initialWorkerState); if(cycleDelay == Timeout.Infinite) { delayTask = Task.Delay(Timeout.Infinite, interruptToken); } this.ExecuteCycleDelay( cycleDelay, delayTask, this.CycleCancellation.Token); } } } this.ClearStateChangeRequests(); this.WorkerState = WorkerState.Stopped; } /// /// Queues a transition in worker state for processing. Returns a task that can be awaited /// when the operation completes. /// /// The request. /// The awaitable task. private Task QueueStateChange(StateChangeRequest request) { lock(this._syncLock) { if(this.StateChangeTask != null) { return this.StateChangeTask; } Task waitingTask = new Task(() => { this.StateChangedEvent.Wait(); lock(this._syncLock) { this.StateChangeTask = null; return this.WorkerState; } }); this.StateChangeTask = waitingTask; this.StateChangedEvent.Reset(); this.StateChangeRequests[request] = true; waitingTask.Start(); this.CycleCancellation.Cancel(); return waitingTask; } } /// /// Processes the state change request by checking pending events and scheduling /// cycle execution accordingly. The is also updated. /// /// Returns true if the execution should be terminated. false otherwise. private Boolean ProcessStateChangeRequests() { lock(this._syncLock) { Boolean hasRequest = false; WorkerState currentState = this.WorkerState; // Update the state in the given priority if(this.StateChangeRequests[StateChangeRequest.Stop] || this.IsDisposing || this.IsDisposed) { hasRequest = true; this.WorkerState = WorkerState.Stopped; } else if(this.StateChangeRequests[StateChangeRequest.Pause]) { hasRequest = true; this.WorkerState = WorkerState.Paused; } else if(this.StateChangeRequests[StateChangeRequest.Start] || this.StateChangeRequests[StateChangeRequest.Resume]) { hasRequest = true; this.WorkerState = WorkerState.Waiting; } // Signals all state changes to continue // as a command has been handled. if(hasRequest) { this.ClearStateChangeRequests(); this.OnStateChangeProcessed(currentState, this.WorkerState); } return hasRequest; } } /// /// Signals all state change requests to set. /// private void ClearStateChangeRequests() { lock(this._syncLock) { // Mark all events as completed this.StateChangeRequests[StateChangeRequest.Start] = false; this.StateChangeRequests[StateChangeRequest.Pause] = false; this.StateChangeRequests[StateChangeRequest.Resume] = false; this.StateChangeRequests[StateChangeRequest.Stop] = false; this.StateChangedEvent.Set(); this.CycleCompletedEvent.Set(); } } } }