2019-02-17 14:08:57 +01:00
|
|
|
|
#if !NETSTANDARD1_3
|
2019-12-04 17:10:06 +01:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
namespace Unosquare.Swan.Abstractions {
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Represents an background worker abstraction with a life cycle and running at a independent thread.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public abstract class RunnerBase {
|
|
|
|
|
private Thread _worker;
|
|
|
|
|
private CancellationTokenSource _cancelTokenSource;
|
|
|
|
|
private ManualResetEvent _workFinished;
|
|
|
|
|
|
2019-02-17 14:08:57 +01:00
|
|
|
|
/// <summary>
|
2019-12-04 17:10:06 +01:00
|
|
|
|
/// Initializes a new instance of the <see cref="RunnerBase"/> class.
|
2019-02-17 14:08:57 +01:00
|
|
|
|
/// </summary>
|
2019-12-04 17:10:06 +01:00
|
|
|
|
/// <param name="isEnabled">if set to <c>true</c> [is enabled].</param>
|
|
|
|
|
protected RunnerBase(Boolean isEnabled) {
|
|
|
|
|
this.Name = this.GetType().Name;
|
|
|
|
|
this.IsEnabled = isEnabled;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the error messages.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <value>
|
|
|
|
|
/// The error messages.
|
|
|
|
|
/// </value>
|
|
|
|
|
public List<String> ErrorMessages { get; } = new List<String>();
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the name.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <value>
|
|
|
|
|
/// The name.
|
|
|
|
|
/// </value>
|
|
|
|
|
public String Name {
|
|
|
|
|
get;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets a value indicating whether this instance is running.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <value>
|
|
|
|
|
/// <c>true</c> if this instance is running; otherwise, <c>false</c>.
|
|
|
|
|
/// </value>
|
|
|
|
|
public Boolean IsRunning {
|
|
|
|
|
get; private set;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets a value indicating whether this instance is enabled.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <value>
|
|
|
|
|
/// <c>true</c> if this instance is enabled; otherwise, <c>false</c>.
|
|
|
|
|
/// </value>
|
|
|
|
|
public Boolean IsEnabled {
|
|
|
|
|
get;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Starts this instance.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public virtual void Start() {
|
|
|
|
|
if(this.IsEnabled == false) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$"Start Requested".Debug(this.Name);
|
|
|
|
|
this._cancelTokenSource = new CancellationTokenSource();
|
|
|
|
|
this._workFinished = new ManualResetEvent(false);
|
|
|
|
|
|
|
|
|
|
this._worker = new Thread(() => {
|
|
|
|
|
_ = this._workFinished.Reset();
|
|
|
|
|
this.IsRunning = true;
|
|
|
|
|
try {
|
|
|
|
|
this.Setup();
|
|
|
|
|
this.DoBackgroundWork(this._cancelTokenSource.Token);
|
|
|
|
|
} catch(ThreadAbortException) {
|
|
|
|
|
$"{nameof(ThreadAbortException)} caught.".Warn(this.Name);
|
|
|
|
|
} catch(Exception ex) {
|
|
|
|
|
$"{ex.GetType()}: {ex.Message}\r\n{ex.StackTrace}".Error(this.Name);
|
|
|
|
|
} finally {
|
|
|
|
|
this.Cleanup();
|
|
|
|
|
_ = this._workFinished?.Set();
|
|
|
|
|
this.IsRunning = false;
|
|
|
|
|
"Stopped Completely".Debug(this.Name);
|
|
|
|
|
}
|
|
|
|
|
}) {
|
|
|
|
|
IsBackground = true,
|
|
|
|
|
Name = $"{this.Name}Thread",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this._worker.Start();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Stops this instance.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public virtual void Stop() {
|
|
|
|
|
if(this.IsEnabled == false || this.IsRunning == false) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$"Stop Requested".Debug(this.Name);
|
|
|
|
|
this._cancelTokenSource.Cancel();
|
|
|
|
|
Int32 waitRetries = 5;
|
|
|
|
|
while(waitRetries >= 1) {
|
|
|
|
|
if(this._workFinished.WaitOne(250)) {
|
|
|
|
|
waitRetries = -1;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
waitRetries--;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(waitRetries < 0) {
|
|
|
|
|
"Workbench stopped gracefully".Debug(this.Name);
|
|
|
|
|
} else {
|
|
|
|
|
"Did not respond to stop request. Aborting thread and waiting . . .".Warn(this.Name);
|
|
|
|
|
this._worker.Abort();
|
|
|
|
|
|
|
|
|
|
if(this._workFinished.WaitOne(5000) == false) {
|
|
|
|
|
"Waited and no response. Worker might have been left in an inconsistent state.".Error(this.Name);
|
|
|
|
|
} else {
|
|
|
|
|
"Waited for worker and it finally responded (OK).".Debug(this.Name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this._workFinished.Dispose();
|
|
|
|
|
this._workFinished = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Setups this instance.
|
|
|
|
|
/// </summary>
|
|
|
|
|
protected virtual void Setup() {
|
|
|
|
|
// empty
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Cleanups this instance.
|
|
|
|
|
/// </summary>
|
|
|
|
|
protected virtual void Cleanup() {
|
|
|
|
|
// empty
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Does the background work.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="ct">The ct.</param>
|
|
|
|
|
protected abstract void DoBackgroundWork(CancellationToken ct);
|
|
|
|
|
}
|
2019-02-17 14:08:57 +01:00
|
|
|
|
}
|
|
|
|
|
#endif
|