namespace Unosquare.Swan.Abstractions { using System; using System.Threading; using System.Threading.Tasks; /// /// 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() { State = AppWorkerState.Stopped; 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 => _workerState; private set { lock (_syncLock) { if (value == _workerState) return; $"Service state changing from {State} to {value}".Debug(GetType().Name); var newState = value; var oldState = _workerState; _workerState = value; StateChanged?.Invoke(this, new AppWorkerStateChangedEventArgs(oldState, newState)); } } } /// /// Gets the cancellation token. /// /// /// The cancellation token. /// public CancellationToken CancellationToken => _tokenSource?.Token ?? default; /// /// Gets a value indicating whether the thread is busy. /// /// /// true if this instance is busy; otherwise, false. /// public bool 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() => CheckIsRunning(); /// /// Service cannot be started because it seems to be currently running. public virtual void Start() { CheckIsRunning(); CreateWorker(); State = AppWorkerState.Running; } /// /// Service cannot be stopped because it is not running. public virtual void Stop() { if (State != AppWorkerState.Running) return; _tokenSource?.Cancel(); "Service stop requested.".Debug(GetType().Name); State = AppWorkerState.Stopped; } /// public void Dispose() => _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(GetType().Name, ex); /// /// This method is called when the user loop has exited. /// protected virtual void OnWorkerThreadExit() => "Service thread is stopping.".Debug(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 (State != AppWorkerState.Stopped) throw new InvalidOperationException("Service cannot be initialized because it seems to be currently running."); } private void CreateWorker() { _tokenSource = new CancellationTokenSource(); _tokenSource.Token.Register(() => { IsBusy = false; OnWorkerThreadExit(); }); Task.Run(async () => { IsBusy = true; try { while (!CancellationToken.IsCancellationRequested) { await WorkerThreadLoop().ConfigureAwait(false); } } catch (AggregateException) { // Ignored } catch (Exception ex) { ex.Log(GetType().Name); OnWorkerThreadLoopException(ex); if (!_tokenSource.IsCancellationRequested) _tokenSource.Cancel(); } }, _tokenSource.Token); } #endregion } }