AbstractWorker.cs 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. using System;
  2. using System.Threading;
  3. using System.Threading.Tasks;
  4. using Quadarax.Foundation.Core.Logging;
  5. using Quadarax.Foundation.Core.Object;
  6. using Quadarax.Foundation.Core.Thread.Extensions;
  7. namespace Quadarax.Foundation.Core.Thread
  8. {
  9. public abstract class AbstractWorker : DisposableObject, IWorker
  10. {
  11. #region *** Private fields ***
  12. private Task? _thread;
  13. private ILog? _log;
  14. private TimeSpan _delay;
  15. private CancellationTokenSource? _cancel;
  16. #endregion
  17. #region *** Properties ***
  18. public bool IsRunning { get; private set; }
  19. public bool IsTransient { get; private set; }
  20. public string Name { get; }
  21. public int CurrentThreadId => System.Threading.Thread.CurrentThread.ManagedThreadId;
  22. public TimeSpan Timeout { get; set; }
  23. public IWorkerContext? Context { get; private set; }
  24. #endregion
  25. #region *** Constructors ***
  26. protected AbstractWorker() : this($"Worker#{Guid.NewGuid():N}", null)
  27. {
  28. }
  29. protected AbstractWorker(IWorkerContext context) : this($"Worker#{Guid.NewGuid():N}", context)
  30. {
  31. }
  32. protected AbstractWorker(string workerName) : this(workerName, null)
  33. {
  34. }
  35. protected AbstractWorker(string workerName, IWorkerContext? context, ILogger? logger = null)
  36. {
  37. if (string.IsNullOrEmpty(workerName))
  38. throw new ArgumentNullException(nameof(workerName));
  39. Name = workerName;
  40. Context = context ?? new BasicWorkerContext();
  41. _log = logger?.GetLogger(GetType());
  42. Timeout = TimeSpan.Zero;
  43. _thread = null;
  44. _cancel = null;
  45. }
  46. #endregion
  47. #region *** Public Operations ***
  48. public void Start(bool silentlyIfNotValidState = false)
  49. {
  50. Start(TimeSpan.Zero, silentlyIfNotValidState);
  51. }
  52. public void Start(TimeSpan runDelay, bool silentlyIfNotValidState = false)
  53. {
  54. if (IsTransient || IsRunning)
  55. if (!silentlyIfNotValidState)
  56. throw new InvalidOperationException(
  57. $"Worker '{Name}' is in transient or running state. Cannot start.");
  58. else
  59. return;
  60. OnBeforeStart();
  61. _cancel = new CancellationTokenSource();
  62. if (Context != null)
  63. {
  64. Context.Cancel = _cancel.Token;
  65. _delay = runDelay;
  66. _thread = Task.Factory.StartNew(() => DoInternal(this), Context.Cancel);
  67. }
  68. IsRunning = true;
  69. }
  70. public void Stop(bool silentlyIfNotValidState = false)
  71. {
  72. if (IsTransient || !IsRunning)
  73. if (!silentlyIfNotValidState)
  74. throw new InvalidOperationException(
  75. $"Worker '{Name}' is in transient or running state. Cannot stop.");
  76. else
  77. return;
  78. IsTransient = true;
  79. Abort();
  80. OnAfterStop();
  81. }
  82. #endregion
  83. #region *** Virtuals and protected ***
  84. protected virtual void OnBeforeStart()
  85. {
  86. }
  87. protected virtual void OnAfterStop()
  88. {
  89. }
  90. #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
  91. protected virtual async Task Do(IWorkerContext? context)
  92. #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
  93. {
  94. throw new NotImplementedException("AbstractWorker.Do() must be overriden.");
  95. }
  96. protected void Log(LogSeverityEnum severity, string message)
  97. {
  98. _log?.Log(severity, message);
  99. }
  100. protected void Log(LogSeverityEnum severity, string message, Exception exception)
  101. {
  102. _log?.Log(severity, message, exception);
  103. }
  104. protected override void OnDisposing()
  105. {
  106. Abort();
  107. Context = null;
  108. _log = null;
  109. }
  110. #endregion
  111. #region *** Private Operations ***
  112. private async void DoInternal(object context)
  113. {
  114. var wk = (AbstractWorker)context;
  115. if (wk._delay != TimeSpan.Zero)
  116. wk._thread?.Wait(wk._delay);
  117. wk.IsTransient = false;
  118. try
  119. {
  120. if (Timeout == TimeSpan.Zero)
  121. await wk.Do(wk.Context);
  122. else
  123. await wk.Do(wk.Context).TimeoutAfter(Timeout);
  124. }
  125. catch (Exception e)
  126. {
  127. wk.Log(LogSeverityEnum.Error, $"Worker '{Name}' failed.", e);
  128. throw;
  129. }
  130. finally
  131. {
  132. wk.IsRunning = false;
  133. }
  134. }
  135. private void Abort()
  136. {
  137. if (_thread == null)
  138. return;
  139. _cancel?.Cancel();
  140. _cancel?.Dispose();
  141. _cancel = null;
  142. _thread = null;
  143. IsRunning = false;
  144. IsTransient = false;
  145. }
  146. #endregion
  147. }
  148. }