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) { DelayProvider = delayProvider; _thread = new Thread(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 (_syncLock) { if (WorkerState == WorkerState.Paused || WorkerState == WorkerState.Waiting) return ResumeAsync(); if (WorkerState != WorkerState.Created) return Task.FromResult(WorkerState); if (IsStopRequested) return Task.FromResult(WorkerState); var task = QueueStateChange(StateChangeRequest.Start); _thread.Start(); return task; } } /// public override Task PauseAsync() { lock (_syncLock) { if (WorkerState != WorkerState.Running && WorkerState != WorkerState.Waiting) return Task.FromResult(WorkerState); return IsStopRequested ? Task.FromResult(WorkerState) : QueueStateChange(StateChangeRequest.Pause); } } /// public override Task ResumeAsync() { lock (_syncLock) { if (WorkerState == WorkerState.Created) return StartAsync(); if (WorkerState != WorkerState.Paused && WorkerState != WorkerState.Waiting) return Task.FromResult(WorkerState); return IsStopRequested ? Task.FromResult(WorkerState) : QueueStateChange(StateChangeRequest.Resume); } } /// public override Task StopAsync() { lock (_syncLock) { if (WorkerState == WorkerState.Stopped || WorkerState == WorkerState.Created) { WorkerState = WorkerState.Stopped; return Task.FromResult(WorkerState); } return 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(int wantedDelay, Task delayTask, CancellationToken token) => DelayProvider?.ExecuteCycleDelay(wantedDelay, delayTask, token); /// protected override void OnDisposing() { lock (_syncLock) { if ((_thread.ThreadState & ThreadState.Unstarted) != ThreadState.Unstarted) _thread.Join(); } } /// /// Implements worker control, execution and delay logic in a loop. /// private void RunWorkerLoop() { while (WorkerState != WorkerState.Stopped && !IsDisposing && !IsDisposed) { CycleStopwatch.Restart(); var interruptToken = CycleCancellation.Token; var period = Period.TotalMilliseconds >= int.MaxValue ? -1 : Convert.ToInt32(Math.Floor(Period.TotalMilliseconds)); var delayTask = Task.Delay(period, interruptToken); var initialWorkerState = WorkerState; // Lock the cycle and capture relevant state valid for this cycle CycleCompletedEvent.Reset(); // Process the tasks that are awaiting if (ProcessStateChangeRequests()) continue; try { if (initialWorkerState == WorkerState.Waiting && !interruptToken.IsCancellationRequested) { // Mark the state as Running WorkerState = WorkerState.Running; // Call the execution logic ExecuteCycleLogic(interruptToken); } } catch (Exception ex) { OnCycleException(ex); } finally { // Update the state WorkerState = initialWorkerState == WorkerState.Paused ? WorkerState.Paused : WorkerState.Waiting; // Signal the cycle has been completed so new cycles can be executed CycleCompletedEvent.Set(); if (!interruptToken.IsCancellationRequested) { var cycleDelay = ComputeCycleDelay(initialWorkerState); if (cycleDelay == Timeout.Infinite) delayTask = Task.Delay(Timeout.Infinite, interruptToken); ExecuteCycleDelay( cycleDelay, delayTask, CycleCancellation.Token); } } } ClearStateChangeRequests(); 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 (_syncLock) { if (StateChangeTask != null) return StateChangeTask; var waitingTask = new Task(() => { StateChangedEvent.Wait(); lock (_syncLock) { StateChangeTask = null; return WorkerState; } }); StateChangeTask = waitingTask; StateChangedEvent.Reset(); StateChangeRequests[request] = true; waitingTask.Start(); 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 bool ProcessStateChangeRequests() { lock (_syncLock) { var hasRequest = false; var currentState = WorkerState; // Update the state in the given priority if (StateChangeRequests[StateChangeRequest.Stop] || IsDisposing || IsDisposed) { hasRequest = true; WorkerState = WorkerState.Stopped; } else if (StateChangeRequests[StateChangeRequest.Pause]) { hasRequest = true; WorkerState = WorkerState.Paused; } else if (StateChangeRequests[StateChangeRequest.Start] || StateChangeRequests[StateChangeRequest.Resume]) { hasRequest = true; WorkerState = WorkerState.Waiting; } // Signals all state changes to continue // as a command has been handled. if (hasRequest) { ClearStateChangeRequests(); OnStateChangeProcessed(currentState, WorkerState); } return hasRequest; } } /// /// Signals all state change requests to set. /// private void ClearStateChangeRequests() { lock (_syncLock) { // Mark all events as completed StateChangeRequests[StateChangeRequest.Start] = false; StateChangeRequests[StateChangeRequest.Pause] = false; StateChangeRequests[StateChangeRequest.Resume] = false; StateChangeRequests[StateChangeRequest.Stop] = false; StateChangedEvent.Set(); CycleCompletedEvent.Set(); } } } }