2019-12-08 21:23:54 +01:00
|
|
|
|
namespace Swan.Threading {
|
|
|
|
|
using System;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 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.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <seealso cref="IWorker" />
|
|
|
|
|
public abstract class ThreadWorkerBase : WorkerBase {
|
|
|
|
|
private readonly Object _syncLock = new Object();
|
|
|
|
|
private readonly Thread _thread;
|
|
|
|
|
|
2019-12-04 18:57:18 +01:00
|
|
|
|
/// <summary>
|
2019-12-08 21:23:54 +01:00
|
|
|
|
/// Initializes a new instance of the <see cref="ThreadWorkerBase"/> class.
|
2019-12-04 18:57:18 +01:00
|
|
|
|
/// </summary>
|
2019-12-08 21:23:54 +01:00
|
|
|
|
/// <param name="name">The name.</param>
|
|
|
|
|
/// <param name="priority">The thread priority.</param>
|
|
|
|
|
/// <param name="period">The interval of cycle execution.</param>
|
|
|
|
|
/// <param name="delayProvider">The cycle delay provide implementation.</param>
|
|
|
|
|
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,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Initializes a new instance of the <see cref="ThreadWorkerBase"/> class.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="name">The name.</param>
|
|
|
|
|
/// <param name="period">The execution interval.</param>
|
|
|
|
|
protected ThreadWorkerBase(String name, TimeSpan period) : this(name, ThreadPriority.Normal, period, WorkerDelayProvider.Default) {
|
|
|
|
|
// placeholder
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Provides an implementation on a cycle delay provider.
|
|
|
|
|
/// </summary>
|
|
|
|
|
protected IWorkerDelayProvider DelayProvider {
|
|
|
|
|
get;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
public override Task<WorkerState> 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<WorkerState> task = this.QueueStateChange(StateChangeRequest.Start);
|
|
|
|
|
this._thread.Start();
|
|
|
|
|
return task;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
public override Task<WorkerState> 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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
public override Task<WorkerState> 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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
public override Task<WorkerState> 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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 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.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="wantedDelay">The remaining delay to wait for in the cycle.</param>
|
|
|
|
|
/// <param name="delayTask">Contains a reference to a task with the scheduled period delay.</param>
|
|
|
|
|
/// <param name="token">The cancellation token to cancel waiting.</param>
|
|
|
|
|
protected virtual void ExecuteCycleDelay(Int32 wantedDelay, Task delayTask, CancellationToken token) =>
|
|
|
|
|
this.DelayProvider?.ExecuteCycleDelay(wantedDelay, delayTask, token);
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
protected override void OnDisposing() {
|
|
|
|
|
lock(this._syncLock) {
|
|
|
|
|
if((this._thread.ThreadState & ThreadState.Unstarted) != ThreadState.Unstarted) {
|
|
|
|
|
this._thread.Join();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Implements worker control, execution and delay logic in a loop.
|
|
|
|
|
/// </summary>
|
|
|
|
|
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
|
2019-12-04 18:57:18 +01:00
|
|
|
|
? WorkerState.Paused
|
2019-12-08 21:23:54 +01:00
|
|
|
|
: 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(
|
2019-12-04 18:57:18 +01:00
|
|
|
|
cycleDelay,
|
|
|
|
|
delayTask,
|
2019-12-08 21:23:54 +01:00
|
|
|
|
this.CycleCancellation.Token);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.ClearStateChangeRequests();
|
|
|
|
|
this.WorkerState = WorkerState.Stopped;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Queues a transition in worker state for processing. Returns a task that can be awaited
|
|
|
|
|
/// when the operation completes.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="request">The request.</param>
|
|
|
|
|
/// <returns>The awaitable task.</returns>
|
|
|
|
|
private Task<WorkerState> QueueStateChange(StateChangeRequest request) {
|
|
|
|
|
lock(this._syncLock) {
|
|
|
|
|
if(this.StateChangeTask != null) {
|
|
|
|
|
return this.StateChangeTask;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Task<WorkerState> waitingTask = new Task<WorkerState>(() => {
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Processes the state change request by checking pending events and scheduling
|
|
|
|
|
/// cycle execution accordingly. The <see cref="WorkerState"/> is also updated.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns>Returns <c>true</c> if the execution should be terminated. <c>false</c> otherwise.</returns>
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Signals all state change requests to set.
|
|
|
|
|
/// </summary>
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-12-04 18:57:18 +01:00
|
|
|
|
}
|