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 } }