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>
|
|
15 public class RunnableComponent : IRunnable, IInitializable, IDisposable {
|
|
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
|
|
83 // completions or the operations.
|
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
|
251
|
91 AsyncOperationDescriptor m_current = AsyncOperationDescriptor.None;
|
|
92
|
|
93 ExecutionState m_state;
|
|
94
|
|
95
|
|
96 protected RunnableComponent(bool initialized) {
|
|
97 State = initialized ? ExecutionState.Ready : ExecutionState.Created;
|
250
|
98 }
|
|
99
|
251
|
100 public Task Completion {
|
|
101 get { return m_current.Task; }
|
|
102 }
|
250
|
103
|
251
|
104 public ExecutionState State {
|
|
105 get { return m_state; }
|
|
106 private set {
|
|
107 if (m_state != value) {
|
|
108 m_state = value;
|
|
109 StateChanged.DispatchEvent(this, new StateChangeEventArgs {
|
|
110 State = value,
|
|
111 LastError = LastError
|
|
112 });
|
|
113 }
|
|
114 }
|
|
115 }
|
|
116
|
|
117 public Exception LastError { get; private set; }
|
250
|
118
|
|
119 public event EventHandler<StateChangeEventArgs> StateChanged;
|
|
120
|
251
|
121 /// <summary>
|
|
122 /// Releases all resources used by the current component regardless of its
|
|
123 /// execution state.
|
|
124 /// </summary>
|
|
125 /// <remarks>
|
|
126 /// Calling to this method may result unexpedted results if the component
|
|
127 /// isn't in the stopped state. Call this method after the component is
|
|
128 /// stopped if needed or if the component is in the failed state.
|
|
129 /// </remarks>
|
250
|
130 public void Dispose() {
|
251
|
131 bool dispose = false;
|
|
132 if (dispose) {
|
250
|
133 Dispose(true);
|
|
134 GC.SuppressFinalize(this);
|
|
135 }
|
|
136 }
|
|
137
|
251
|
138 ~RunnableComponent() {
|
|
139 Dispose(false);
|
|
140 }
|
|
141
|
|
142 /// <summary>
|
|
143 /// Releases all resources used by the current component regardless of its
|
|
144 /// execution state.
|
|
145 /// </summary>
|
|
146 /// <param name="disposing">Indicates that the component is disposed
|
|
147 /// during a normal disposing or during GC.</param>
|
250
|
148 protected virtual void Dispose(bool disposing) {
|
251
|
149 }
|
|
150
|
|
151 public void Initialize() {
|
|
152 var cookie = new object();
|
|
153 if (MoveInitialize(cookie))
|
|
154 ScheduleTask(InitializeInternalAsync, CancellationToken.None, cookie);
|
|
155 }
|
|
156
|
|
157 /// <summary>
|
|
158 /// This method is used for initialization during a component creation.
|
|
159 /// </summary>
|
|
160 /// <param name="ct">A cancellation token for this operation</param>
|
|
161 /// <remarks>
|
|
162 /// This method should be used for short and mostly syncronous operations,
|
|
163 /// other operations which require time to run shoud be placed in
|
252
|
164 /// <see cref="StartInternalAsync(CancellationToken)"/> method.
|
251
|
165 /// </remarks>
|
|
166 protected virtual Task InitializeInternalAsync(CancellationToken ct) {
|
|
167 return Task.CompletedTask;
|
250
|
168 }
|
|
169
|
|
170 public void Start(CancellationToken ct) {
|
251
|
171 var cookie = new object();
|
|
172 if (MoveStart(cookie))
|
252
|
173 ScheduleTask(StartInternalAsync, ct, cookie);
|
251
|
174 }
|
|
175
|
252
|
176 protected virtual Task StartInternalAsync(CancellationToken ct) {
|
251
|
177 return Task.CompletedTask;
|
|
178 }
|
|
179
|
|
180 public void Stop(CancellationToken ct) {
|
|
181 var cookie = new object();
|
|
182 if (MoveStop(cookie))
|
|
183 ScheduleTask(StopAsync, ct, cookie);
|
|
184 }
|
|
185
|
|
186 async Task StopAsync(CancellationToken ct) {
|
|
187 m_current.Cancel();
|
|
188 await Completion;
|
|
189
|
|
190 ct.ThrowIfCancellationRequested();
|
|
191
|
|
192 await StopInternalAsync(ct);
|
|
193 }
|
|
194
|
|
195 protected virtual Task StopInternalAsync(CancellationToken ct) {
|
|
196 return Task.CompletedTask;
|
|
197 }
|
|
198
|
|
199
|
|
200 #region state management
|
|
201
|
|
202 bool MoveInitialize(object cookie) {
|
|
203 lock (m_lock) {
|
|
204 if (State != ExecutionState.Created)
|
|
205 return false;
|
|
206 State = ExecutionState.Initializing;
|
|
207 m_cookie = cookie;
|
|
208 return true;
|
|
209 }
|
|
210 }
|
|
211
|
|
212 bool MoveStart(object cookie) {
|
|
213 lock (m_lock) {
|
|
214 if (State != ExecutionState.Ready)
|
|
215 return false;
|
|
216 State = ExecutionState.Starting;
|
|
217 m_cookie = cookie;
|
|
218 return true;
|
|
219 }
|
|
220 }
|
|
221
|
|
222 bool MoveStop(object cookie) {
|
|
223 lock (m_lock) {
|
|
224 if (State != ExecutionState.Starting && State != ExecutionState.Running)
|
|
225 return false;
|
|
226 State = ExecutionState.Stopping;
|
|
227 m_cookie = cookie;
|
|
228 return true;
|
|
229 }
|
|
230 }
|
|
231
|
|
232 void MoveSuccess(object cookie) {
|
|
233 lock (m_lock) {
|
|
234 if (m_cookie != cookie)
|
|
235 return;
|
|
236 switch (State) {
|
|
237 case ExecutionState.Initializing:
|
|
238 State = ExecutionState.Ready;
|
|
239 break;
|
|
240 case ExecutionState.Starting:
|
|
241 State = ExecutionState.Running;
|
|
242 break;
|
|
243 case ExecutionState.Stopping:
|
|
244 State = ExecutionState.Stopped;
|
|
245 break;
|
250
|
246 }
|
|
247 }
|
|
248 }
|
|
249
|
251
|
250 void MoveFailed(Exception err, object cookie) {
|
|
251 lock (m_lock) {
|
|
252 if (m_cookie != cookie)
|
|
253 return;
|
|
254 LastError = err;
|
|
255 State = ExecutionState.Failed;
|
|
256 }
|
250
|
257 }
|
|
258
|
251
|
259
|
250
|
260
|
251
|
261 protected async void ScheduleTask(Func<CancellationToken, Task> next, CancellationToken ct, object cookie) {
|
|
262 try {
|
|
263 m_current = AsyncOperationDescriptor.Create(next, ct);
|
|
264 await m_current.Task;
|
|
265 MoveSuccess(cookie);
|
|
266 } catch (Exception e) {
|
|
267 MoveFailed(e, cookie);
|
|
268 }
|
250
|
269 }
|
251
|
270
|
|
271 #endregion
|
250
|
272 }
|
|
273 } |