using System;
using System.Threading;
using System.Threading.Tasks;
namespace Unosquare.Swan.Abstractions {
///
/// A base implementation of an Application service containing a worker task that performs background processing.
///
///
/// The following code describes how to implement the class.
///
/// using System;
/// using System.Threading.Tasks;
/// using Unosquare.Swan;
/// using Unosquare.Swan.Abstractions;
///
/// class Worker : AppWorkerBase
/// {
/// // an action that will be executed if the worker is stopped
/// public Action OnExit { get; set; }
///
/// // override the base loop method, this is the code will
/// // execute until the cancellation token is canceled.
/// protected override Task WorkerThreadLoop()
/// {
/// // delay a second and then proceed
/// await Task.Delay(TimeSpan.FromMilliseconds(1000), CancellationToken);
///
/// // just print out this
/// $"Working...".WriteLine();
/// }
///
/// // Once the worker is stopped this code will be executed
/// protected override void OnWorkerThreadExit()
/// {
/// // execute the base method
/// base.OnWorkerThreadExit();
///
/// // then if the OnExit Action is not null execute it
/// OnExit?.Invoke();
/// }
/// }
///
///
public abstract class AppWorkerBase
: IWorker, IDisposable {
private readonly Object _syncLock = new Object();
private AppWorkerState _workerState = AppWorkerState.Stopped;
private CancellationTokenSource _tokenSource;
///
/// Initializes a new instance of the class.
///
protected AppWorkerBase() {
this.State = AppWorkerState.Stopped;
this.IsBusy = false;
}
///
/// Occurs when [state changed].
///
public event EventHandler StateChanged;
#region Properties
///
/// Gets the state of the application service.
/// In other words, useful to know whether the service is running.
///
///
/// The state.
///
public AppWorkerState State {
get => this._workerState;
private set {
lock(this._syncLock) {
if(value == this._workerState) {
return;
}
$"Service state changing from {this.State} to {value}".Debug(this.GetType().Name);
AppWorkerState newState = value;
AppWorkerState oldState = this._workerState;
this._workerState = value;
StateChanged?.Invoke(this, new AppWorkerStateChangedEventArgs(oldState, newState));
}
}
}
///
/// Gets the cancellation token.
///
///
/// The cancellation token.
///
public CancellationToken CancellationToken => this._tokenSource?.Token ?? default;
///
/// Gets a value indicating whether the thread is busy.
///
///
/// true if this instance is busy; otherwise, false.
///
public Boolean IsBusy {
get; private set;
}
#endregion
#region AppWorkerBase Methods
///
/// Performs internal service initialization tasks required before starting the service.
///
/// Service cannot be initialized because it seems to be currently running.
public virtual void Initialize() => this.CheckIsRunning();
///
/// Service cannot be started because it seems to be currently running.
public virtual void Start() {
this.CheckIsRunning();
this.CreateWorker();
this.State = AppWorkerState.Running;
}
///
/// Service cannot be stopped because it is not running.
public virtual void Stop() {
if(this.State != AppWorkerState.Running) {
return;
}
this._tokenSource?.Cancel();
"Service stop requested.".Debug(this.GetType().Name);
this.State = AppWorkerState.Stopped;
}
///
public void Dispose() => this._tokenSource?.Dispose();
#endregion
#region Abstract and Virtual Methods
///
/// Called when an unhandled exception is thrown.
///
/// The ex.
protected virtual void OnWorkerThreadLoopException(Exception ex)
=> "Service exception detected.".Debug(this.GetType().Name, ex);
///
/// This method is called when the user loop has exited.
///
protected virtual void OnWorkerThreadExit() => "Service thread is stopping.".Debug(this.GetType().Name);
///
/// Implement this method as a loop that checks whether CancellationPending has been set to true
/// If so, immediately exit the loop.
///
/// A task representing the execution of the worker.
protected abstract Task WorkerThreadLoop();
private void CheckIsRunning() {
if(this.State != AppWorkerState.Stopped) {
throw new InvalidOperationException("Service cannot be initialized because it seems to be currently running.");
}
}
private void CreateWorker() {
this._tokenSource = new CancellationTokenSource();
_ = this._tokenSource.Token.Register(() => {
this.IsBusy = false;
this.OnWorkerThreadExit();
});
_ = Task.Run(async () => {
this.IsBusy = true;
try {
while(!this.CancellationToken.IsCancellationRequested) {
await this.WorkerThreadLoop().ConfigureAwait(false);
}
} catch(AggregateException) {
// Ignored
} catch(Exception ex) {
ex.Log(this.GetType().Name);
this.OnWorkerThreadLoopException(ex);
if(!this._tokenSource.IsCancellationRequested) {
this._tokenSource.Cancel();
}
}
},
this._tokenSource.Token);
}
#endregion
}
}