AbstractWorker.cs 5.6 KB

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