203 lines
6.4 KiB
C#
203 lines
6.4 KiB
C#
using System;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace Unosquare.Swan.Abstractions {
|
|
/// <summary>
|
|
/// A base implementation of an Application service containing a worker task that performs background processing.
|
|
/// </summary>
|
|
/// <example>
|
|
/// The following code describes how to implement the <see cref="AppWorkerBase"/> class.
|
|
/// <code>
|
|
/// 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();
|
|
/// }
|
|
/// }
|
|
/// </code>
|
|
/// </example>
|
|
public abstract class AppWorkerBase
|
|
: IWorker, IDisposable {
|
|
private readonly Object _syncLock = new Object();
|
|
private AppWorkerState _workerState = AppWorkerState.Stopped;
|
|
private CancellationTokenSource _tokenSource;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="AppWorkerBase"/> class.
|
|
/// </summary>
|
|
protected AppWorkerBase() {
|
|
this.State = AppWorkerState.Stopped;
|
|
this.IsBusy = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Occurs when [state changed].
|
|
/// </summary>
|
|
public event EventHandler<AppWorkerStateChangedEventArgs> StateChanged;
|
|
|
|
#region Properties
|
|
|
|
/// <summary>
|
|
/// Gets the state of the application service.
|
|
/// In other words, useful to know whether the service is running.
|
|
/// </summary>
|
|
/// <value>
|
|
/// The state.
|
|
/// </value>
|
|
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));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the cancellation token.
|
|
/// </summary>
|
|
/// <value>
|
|
/// The cancellation token.
|
|
/// </value>
|
|
public CancellationToken CancellationToken => this._tokenSource?.Token ?? default;
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether the thread is busy.
|
|
/// </summary>
|
|
/// <value>
|
|
/// <c>true</c> if this instance is busy; otherwise, <c>false</c>.
|
|
/// </value>
|
|
public Boolean IsBusy {
|
|
get; private set;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region AppWorkerBase Methods
|
|
|
|
/// <summary>
|
|
/// Performs internal service initialization tasks required before starting the service.
|
|
/// </summary>
|
|
/// <exception cref="InvalidOperationException">Service cannot be initialized because it seems to be currently running.</exception>
|
|
public virtual void Initialize() => this.CheckIsRunning();
|
|
|
|
/// <inheritdoc/>
|
|
/// <exception cref="InvalidOperationException">Service cannot be started because it seems to be currently running.</exception>
|
|
public virtual void Start() {
|
|
this.CheckIsRunning();
|
|
|
|
this.CreateWorker();
|
|
this.State = AppWorkerState.Running;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
/// <exception cref="InvalidOperationException">Service cannot be stopped because it is not running.</exception>
|
|
public virtual void Stop() {
|
|
if(this.State != AppWorkerState.Running) {
|
|
return;
|
|
}
|
|
|
|
this._tokenSource?.Cancel();
|
|
"Service stop requested.".Debug(this.GetType().Name);
|
|
this.State = AppWorkerState.Stopped;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void Dispose() => this._tokenSource?.Dispose();
|
|
|
|
#endregion
|
|
|
|
#region Abstract and Virtual Methods
|
|
|
|
/// <summary>
|
|
/// Called when an unhandled exception is thrown.
|
|
/// </summary>
|
|
/// <param name="ex">The ex.</param>
|
|
protected virtual void OnWorkerThreadLoopException(Exception ex)
|
|
=> "Service exception detected.".Debug(this.GetType().Name, ex);
|
|
|
|
/// <summary>
|
|
/// This method is called when the user loop has exited.
|
|
/// </summary>
|
|
protected virtual void OnWorkerThreadExit() => "Service thread is stopping.".Debug(this.GetType().Name);
|
|
|
|
/// <summary>
|
|
/// Implement this method as a loop that checks whether CancellationPending has been set to true
|
|
/// If so, immediately exit the loop.
|
|
/// </summary>
|
|
/// <returns>A task representing the execution of the worker.</returns>
|
|
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
|
|
}
|
|
} |