250
|
1 using System;
|
251
|
2 using System.Diagnostics;
|
250
|
3 using System.Threading;
|
|
4 using System.Threading.Tasks;
|
|
5
|
251
|
6 namespace Implab.Components {
|
|
7 /// <summary>
|
|
8 /// Base class for implementing components which support start and stop operations,
|
|
9 /// such components may represent running services.
|
|
10 /// </summary>
|
|
11 /// <remarks>
|
|
12 /// This class provides a basic lifecycle from the creation to the
|
|
13 /// termination of the component.
|
|
14 /// </remarks>
|
256
|
15 public class RunnableComponent : IAsyncComponent, IRunnable, IInitializable, IDisposable {
|
251
|
16
|
|
17 /// <summary>
|
252
|
18 /// This class bounds <see cref="CancellationTokenSource"/> lifetime to the task,
|
251
|
19 /// when the task completes the associated token source will be disposed.
|
|
20 /// </summary>
|
|
21 class AsyncOperationDescriptor {
|
|
22
|
|
23 public static AsyncOperationDescriptor None { get; } = new AsyncOperationDescriptor();
|
|
24
|
|
25 readonly CancellationTokenSource m_cts;
|
|
26
|
|
27 bool m_done;
|
|
28
|
|
29 public CancellationToken Token {
|
|
30 get { return m_cts == null ? CancellationToken.None : m_cts.Token; }
|
|
31 }
|
|
32
|
|
33 public Task Task { get; private set; }
|
|
34
|
|
35 private AsyncOperationDescriptor(Task task, CancellationTokenSource cts) {
|
|
36 m_cts = cts;
|
|
37 Task = Chain(task);
|
|
38 }
|
|
39
|
|
40 private AsyncOperationDescriptor() {
|
|
41 Task = Task.CompletedTask;
|
|
42 }
|
250
|
43
|
251
|
44 public void Cancel() {
|
|
45 if (m_cts != null) {
|
|
46 lock (m_cts) {
|
|
47 if (!m_done)
|
|
48 m_cts.Cancel();
|
|
49 }
|
|
50 }
|
|
51 }
|
|
52
|
|
53 void Done() {
|
|
54 if (m_cts != null) {
|
|
55 lock (m_cts) {
|
|
56 m_done = true;
|
|
57 m_cts.Dispose();
|
|
58 }
|
|
59 } else {
|
|
60 m_done = true;
|
|
61 }
|
|
62 }
|
|
63
|
|
64 async Task Chain(Task other) {
|
|
65 try {
|
|
66 await other;
|
|
67 } finally {
|
|
68 Done();
|
|
69 }
|
|
70 }
|
|
71
|
|
72 public static AsyncOperationDescriptor Create(Func<CancellationToken, Task> factory, CancellationToken ct) {
|
|
73 var cts = ct.CanBeCanceled ?
|
|
74 CancellationTokenSource.CreateLinkedTokenSource(ct) :
|
|
75 new CancellationTokenSource();
|
|
76
|
|
77 return new AsyncOperationDescriptor(factory(cts.Token), cts);
|
|
78 }
|
|
79
|
|
80 }
|
|
81
|
|
82 // this lock is used to synchronize state flow of the component during
|
256
|
83 // processing calls from a client and internal processes.
|
250
|
84 readonly object m_lock = new object();
|
|
85
|
251
|
86 // current operation cookie, used to check wheather a call to
|
|
87 // MoveSuccess/MoveFailed method belongs to the current
|
|
88 // operation, if cookies didn't match ignore completion result.
|
|
89 object m_cookie;
|
250
|
90
|
256
|
91 // AsyncOperationDscriptor aggregates a task and it's cancellation token
|
251
|
92 AsyncOperationDescriptor m_current = AsyncOperationDescriptor.None;
|
|
93
|
|
94 ExecutionState m_state;
|
|
95
|
|
96
|
|
97 protected RunnableComponent(bool initialized) {
|
|
98 State = initialized ? ExecutionState.Ready : ExecutionState.Created;
|
250
|
99 }
|
|
100
|
251
|
101 public Task Completion {
|
|
102 get { return m_current.Task; }
|
|
103 }
|
250
|
104
|
251
|
105 public ExecutionState State {
|
|
106 get { return m_state; }
|
|
107 private set {
|
|
108 if (m_state != value) {
|
|
109 m_state = value;
|
|
110 StateChanged.DispatchEvent(this, new StateChangeEventArgs {
|
|
111 State = value,
|
|
112 LastError = LastError
|
|
113 });
|
|
114 }
|
|
115 }
|
|
116 }
|
|
117
|
|
118 public Exception LastError { get; private set; }
|
250
|
119
|
|
120 public event EventHandler<StateChangeEventArgs> StateChanged;
|
|
121
|
251
|
122 /// <summary>
|
|
123 /// Releases all resources used by the current component regardless of its
|
|
124 /// execution state.
|
|
125 /// </summary>
|
|
126 /// <remarks>
|
|
127 /// Calling to this method may result unexpedted results if the component
|
|
128 /// isn't in the stopped state. Call this method after the component is
|
|
129 /// stopped if needed or if the component is in the failed state.
|
|
130 /// </remarks>
|
250
|
131 public void Dispose() {
|
251
|
132 bool dispose = false;
|
|
133 if (dispose) {
|
250
|
134 Dispose(true);
|
|
135 GC.SuppressFinalize(this);
|
|
136 }
|
|
137 }
|
|
138
|
251
|
139 ~RunnableComponent() {
|
|
140 Dispose(false);
|
|
141 }
|
|
142
|
|
143 /// <summary>
|
|
144 /// Releases all resources used by the current component regardless of its
|
|
145 /// execution state.
|
|
146 /// </summary>
|
|
147 /// <param name="disposing">Indicates that the component is disposed
|
|
148 /// during a normal disposing or during GC.</param>
|
250
|
149 protected virtual void Dispose(bool disposing) {
|
251
|
150 }
|
|
151
|
|
152 public void Initialize() {
|
|
153 var cookie = new object();
|
|
154 if (MoveInitialize(cookie))
|
|
155 ScheduleTask(InitializeInternalAsync, CancellationToken.None, cookie);
|
256
|
156 else
|
|
157 throw new InvalidOperationException();
|
251
|
158 }
|
|
159
|
|
160 /// <summary>
|
|
161 /// This method is used for initialization during a component creation.
|
|
162 /// </summary>
|
|
163 /// <param name="ct">A cancellation token for this operation</param>
|
|
164 /// <remarks>
|
|
165 /// This method should be used for short and mostly syncronous operations,
|
|
166 /// other operations which require time to run shoud be placed in
|
252
|
167 /// <see cref="StartInternalAsync(CancellationToken)"/> method.
|
251
|
168 /// </remarks>
|
|
169 protected virtual Task InitializeInternalAsync(CancellationToken ct) {
|
|
170 return Task.CompletedTask;
|
250
|
171 }
|
|
172
|
|
173 public void Start(CancellationToken ct) {
|
251
|
174 var cookie = new object();
|
|
175 if (MoveStart(cookie))
|
252
|
176 ScheduleTask(StartInternalAsync, ct, cookie);
|
256
|
177 else
|
|
178 throw new InvalidOperationException();
|
251
|
179 }
|
|
180
|
252
|
181 protected virtual Task StartInternalAsync(CancellationToken ct) {
|
251
|
182 return Task.CompletedTask;
|
|
183 }
|
|
184
|
|
185 public void Stop(CancellationToken ct) {
|
|
186 var cookie = new object();
|
|
187 if (MoveStop(cookie))
|
|
188 ScheduleTask(StopAsync, ct, cookie);
|
256
|
189 else
|
|
190 throw new InvalidOperationException();
|
251
|
191 }
|
|
192
|
|
193 async Task StopAsync(CancellationToken ct) {
|
|
194 m_current.Cancel();
|
|
195 await Completion;
|
|
196
|
|
197 ct.ThrowIfCancellationRequested();
|
|
198
|
|
199 await StopInternalAsync(ct);
|
|
200 }
|
|
201
|
|
202 protected virtual Task StopInternalAsync(CancellationToken ct) {
|
|
203 return Task.CompletedTask;
|
|
204 }
|
|
205
|
256
|
206 protected void Fail(Exception err) {
|
|
207 lock(m_lock) {
|
|
208 if (m_state != ExecutionState.Running)
|
|
209 return;
|
|
210 m_cookie = new object();
|
|
211 LastError = err;
|
|
212 State = ExecutionState.Failed;
|
|
213 }
|
|
214 }
|
|
215
|
251
|
216
|
|
217 #region state management
|
|
218
|
|
219 bool MoveInitialize(object cookie) {
|
|
220 lock (m_lock) {
|
|
221 if (State != ExecutionState.Created)
|
|
222 return false;
|
|
223 State = ExecutionState.Initializing;
|
|
224 m_cookie = cookie;
|
|
225 return true;
|
|
226 }
|
|
227 }
|
|
228
|
|
229 bool MoveStart(object cookie) {
|
|
230 lock (m_lock) {
|
|
231 if (State != ExecutionState.Ready)
|
|
232 return false;
|
|
233 State = ExecutionState.Starting;
|
|
234 m_cookie = cookie;
|
|
235 return true;
|
|
236 }
|
|
237 }
|
|
238
|
|
239 bool MoveStop(object cookie) {
|
|
240 lock (m_lock) {
|
|
241 if (State != ExecutionState.Starting && State != ExecutionState.Running)
|
|
242 return false;
|
|
243 State = ExecutionState.Stopping;
|
|
244 m_cookie = cookie;
|
|
245 return true;
|
|
246 }
|
|
247 }
|
|
248
|
|
249 void MoveSuccess(object cookie) {
|
|
250 lock (m_lock) {
|
|
251 if (m_cookie != cookie)
|
|
252 return;
|
|
253 switch (State) {
|
|
254 case ExecutionState.Initializing:
|
|
255 State = ExecutionState.Ready;
|
|
256 break;
|
|
257 case ExecutionState.Starting:
|
|
258 State = ExecutionState.Running;
|
|
259 break;
|
|
260 case ExecutionState.Stopping:
|
|
261 State = ExecutionState.Stopped;
|
|
262 break;
|
250
|
263 }
|
|
264 }
|
|
265 }
|
|
266
|
251
|
267 void MoveFailed(Exception err, object cookie) {
|
|
268 lock (m_lock) {
|
|
269 if (m_cookie != cookie)
|
|
270 return;
|
|
271 LastError = err;
|
|
272 State = ExecutionState.Failed;
|
|
273 }
|
250
|
274 }
|
|
275
|
256
|
276 void ScheduleTask(Func<CancellationToken, Task> next, CancellationToken ct, object cookie) {
|
250
|
277
|
256
|
278 m_current = AsyncOperationDescriptor.Create(async (x) => {
|
|
279 try {
|
|
280 await next(x);
|
|
281 MoveSuccess(cookie);
|
|
282 } catch (Exception e) {
|
|
283 MoveFailed(e, cookie);
|
|
284 }
|
|
285 }, ct);
|
250
|
286 }
|
251
|
287
|
|
288 #endregion
|
250
|
289 }
|
|
290 } |