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