/
TaskRunner.cs
212 lines (183 loc) · 7.57 KB
/
TaskRunner.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
namespace RecurrentTasks
{
using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
public class TaskRunner<TRunnable> : ITask<TRunnable>
where TRunnable: IRunnable
{
private readonly EventWaitHandle breakEvent = new ManualResetEvent(false);
private readonly EventWaitHandle runImmediately = new AutoResetEvent(false);
private ILogger logger;
private Task mainTask;
/// <param name="loggerFactory">Фабрика для создания логгера</param>
/// <param name="interval">Интервал (периодичность) запуска задачи</param>
/// <param name="serviceScopeFactory">Фабрика для создания Scope (при запуске задачи)</param>
public TaskRunner(ILoggerFactory loggerFactory, IServiceScopeFactory serviceScopeFactory)
{
this.logger = loggerFactory.CreateLogger(this.GetType().FullName);
ServiceScopeFactory = serviceScopeFactory;
RunStatus = new TaskRunStatus();
}
public event EventHandler<ExceptionEventArgs> AfterRunFail;
TaskRunStatus ITask.RunStatus { get { return RunStatus; } }
public TaskRunStatus RunStatus { get; protected set; }
public bool IsStarted
{
get
{
return mainTask != null;
}
}
public bool IsRunningRightNow { get; private set; }
public CultureInfo RunningCulture { get; set; }
public TimeSpan Interval { get; set; }
private IServiceScopeFactory ServiceScopeFactory { get; set; }
public void Start(TimeSpan firstRunDelay)
{
if (firstRunDelay < TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException(nameof(firstRunDelay), "First run delay can't be negative");
}
if (Interval < TimeSpan.Zero)
{
throw new InvalidOperationException("Interval can't be negative");
}
logger.LogInformation("Start() called...");
if (mainTask != null)
{
throw new InvalidOperationException("Already started");
}
breakEvent.Reset();
mainTask = Task.Run(() => MainLoop(firstRunDelay));
}
public void Stop()
{
logger.LogInformation("Stop() called...");
if (mainTask == null)
{
throw new InvalidOperationException("Can't stop without start");
}
breakEvent.Set();
}
public void TryRunImmediately()
{
if (mainTask == null)
{
throw new InvalidOperationException("Can't run without Start");
}
runImmediately.Set();
}
protected void MainLoop(TimeSpan firstRunDelay)
{
logger.LogInformation("MainLoop() started. Running...");
var events = new WaitHandle[] { breakEvent, runImmediately };
var sleepInterval = firstRunDelay;
while (true)
{
logger.LogDebug("Sleeping for {0}...", sleepInterval);
RunStatus.NextRunTime = DateTimeOffset.Now.Add(sleepInterval);
var signaled = WaitHandle.WaitAny(events, sleepInterval);
if (signaled == 0) // index of signalled. zero is for 'breakEvent'
{
// must stop and quit
logger.LogWarning("BreakEvent is set, stopping...");
mainTask = null;
break;
}
logger.LogDebug("It is time! Creating scope...");
using (var scope = ServiceScopeFactory.CreateScope())
{
if (RunningCulture != null)
{
logger.LogDebug("Switching to {0} CultureInfo...", RunningCulture.Name);
#if NET451
Thread.CurrentThread.CurrentCulture = RunningCulture;
Thread.CurrentThread.CurrentUICulture = RunningCulture;
#else
CultureInfo.CurrentCulture = RunningCulture;
CultureInfo.CurrentUICulture = RunningCulture;
#endif
}
try
{
OnBeforeRun();
IsRunningRightNow = true;
var startTime = DateTimeOffset.Now;
var runnable = (TRunnable) scope.ServiceProvider.GetRequiredService(typeof(TRunnable));
logger.LogInformation("Calling Run()...");
runnable.Run(this);
logger.LogInformation("Done.");
RunStatus.LastRunTime = startTime;
RunStatus.LastResult = TaskRunResult.Success;
RunStatus.LastSuccessTime = DateTimeOffset.Now;
RunStatus.FirstFailTime = DateTimeOffset.MinValue;
RunStatus.FailsCount = 0;
RunStatus.LastException = null;
IsRunningRightNow = false;
OnAfterRunSuccess();
}
catch (Exception ex)
{
logger.LogWarning(0, ex, "Ooops, error (ignoring, see RunStatus.LastException or handle AfterRunFail event)");
RunStatus.LastResult = TaskRunResult.Fail;
RunStatus.LastException = ex;
if (RunStatus.FailsCount == 0)
{
RunStatus.FirstFailTime = DateTimeOffset.Now;
}
RunStatus.FailsCount++;
IsRunningRightNow = false;
OnAfterRunFail();
try
{
AfterRunFail?.Invoke(this, new ExceptionEventArgs(ex));
}
catch (Exception ex2)
{
logger.LogError(0, ex2, "Error while processing AfterRunFail event (ignored)");
}
}
finally
{
IsRunningRightNow = false;
}
}
if (Interval.Ticks == 0)
{
logger.LogWarning("Interval equal to zero. Stopping...");
breakEvent.Set();
}
else
{
sleepInterval = Interval; // return to normal (important after first run only)
}
}
logger.LogInformation("MainLoop() finished.");
}
/// <summary>
/// Called before Run() is called (even before IsRunningRightNow set to true)
/// </summary>
protected virtual void OnBeforeRun()
{
// nothing
}
/// <summary>
/// Called after Run() sucessfully finished (after IsRunningRightNow set to false)
/// </summary>
protected virtual void OnAfterRunSuccess()
{
// nothing
}
/// <summary>
/// Called after Run() falied (after IsRunningRightNow set to false)
/// </summary>
protected virtual void OnAfterRunFail()
{
// nothing
}
}
}