AbstractWorker.cs 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  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 IQLog _log;
  15. private TimeSpan _delay;
  16. private CancellationTokenSource _cancel;
  17. #endregion
  18. #region *** Properties ***
  19. public bool IsRunning { get; private set; }
  20. public bool IsTransient { get; private set; }
  21. public string Name { get; }
  22. public int CurrentThreadId => System.Threading.Thread.CurrentThread.ManagedThreadId;
  23. public TimeSpan Timeout { get; set; }
  24. protected IWorkerContext Context { get; private set; }
  25. public long IterationCount { get; protected set; } = 0;
  26. #endregion
  27. #region *** Constructors ***
  28. protected AbstractWorker() : this($"Worker#{Guid.NewGuid().ToBase32String()}", null)
  29. {
  30. }
  31. protected AbstractWorker(IWorkerContext context) : this($"Worker#{Guid.NewGuid().ToBase32String()}", context)
  32. {
  33. }
  34. protected AbstractWorker(string workerName) : this(workerName, null)
  35. {
  36. }
  37. protected AbstractWorker(string workerName, IWorkerContext context, IQLogger 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. }
  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. _delay = runDelay;
  63. _thread = Task.Factory.StartNew(() => DoInternal(this), _cancel.Token);
  64. IsRunning = true;
  65. }
  66. public void Stop(bool silentlyIfNotValidState = false)
  67. {
  68. if (IsTransient || !IsRunning)
  69. if (!silentlyIfNotValidState)
  70. throw new InvalidOperationException(
  71. $"Worker '{Name}' is in transient or running state. Cannot start.");
  72. else
  73. return;
  74. IsTransient = true;
  75. Abort();
  76. OnAfterStop();
  77. }
  78. #endregion
  79. #region *** Virtuals and protected ***
  80. protected virtual void OnBeforeStart()
  81. {
  82. }
  83. protected virtual void OnAfterStop()
  84. {
  85. }
  86. protected virtual async Task Do(IWorkerContext context)
  87. {
  88. throw new NotImplementedException("AbstractWorker.Do() must be overriden.");
  89. }
  90. protected void Log(LogSeverityEnum severity, string message)
  91. {
  92. _log?.Log(severity, message);
  93. }
  94. protected void Log(LogSeverityEnum severity, string message, Exception exception)
  95. {
  96. _log?.Log(severity, message, exception);
  97. }
  98. protected override void OnDisposing()
  99. {
  100. Abort();
  101. Context = null;
  102. _log = null;
  103. }
  104. #endregion
  105. #region *** Private Operations ***
  106. private async void DoInternal(object context)
  107. {
  108. var wk = (AbstractWorker)context;
  109. if (wk._delay != TimeSpan.Zero)
  110. wk._thread.Wait(wk._delay);
  111. wk.IsTransient = false;
  112. try
  113. {
  114. if (Timeout == TimeSpan.Zero)
  115. await wk.Do(wk.Context);
  116. else
  117. await wk.Do(wk.Context).TimeoutAfter(Timeout);
  118. IterationCount++;
  119. }
  120. catch (Exception e)
  121. {
  122. wk.Log(LogSeverityEnum.Error, $"Worker '{Name}' failed.", e);
  123. throw;
  124. }
  125. finally
  126. {
  127. wk.IsRunning = false;
  128. }
  129. }
  130. private void Abort()
  131. {
  132. if (_thread == null)
  133. return;
  134. _cancel.Cancel();
  135. _cancel.Dispose();
  136. _cancel = null;
  137. _thread = null;
  138. IsRunning = false;
  139. IsTransient = false;
  140. }
  141. #endregion
  142. }
  143. }