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