Mercurial > pub > ImplabNet
comparison Implab/Components/RunnableComponent.cs @ 251:7c7e9ad6fe4a v3
Prerelease version of RunnableComponent
Added draft messaging interfaces
Added more more helpers to Xml/SerializationHelpers
author | cin |
---|---|
date | Sun, 11 Feb 2018 00:49:51 +0300 |
parents | 9f63dade3a40 |
children | 6f4630d0bcd9 |
comparison
equal
deleted
inserted
replaced
250:9f63dade3a40 | 251:7c7e9ad6fe4a |
---|---|
1 using System; | 1 using System; |
2 using System.Diagnostics; | |
2 using System.Threading; | 3 using System.Threading; |
3 using System.Threading.Tasks; | 4 using System.Threading.Tasks; |
4 | 5 |
5 namespace Implab.Components | 6 namespace Implab.Components { |
6 { | 7 /// <summary> |
7 public class RunnableComponent : IRunnable { | 8 /// Base class for implementing components which support start and stop operations, |
8 | 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> | |
18 /// This class bound <see cref="CancellationTokenSource"/> lifetime to the task, | |
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 } | |
43 | |
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. | |
9 readonly object m_lock = new object(); | 84 readonly object m_lock = new object(); |
10 | 85 |
11 CancellationTokenSource m_cts; | 86 // current operation cookie, used to check wheather a call to |
12 | 87 // MoveSuccess/MoveFailed method belongs to the current |
13 public Task<ExecutionState> Completion { | 88 // operation, if cookies didn't match ignore completion result. |
14 get; | 89 object m_cookie; |
15 private set; | 90 |
16 } | 91 AsyncOperationDescriptor m_current = AsyncOperationDescriptor.None; |
17 | 92 |
18 public ExecutionState State => throw new NotImplementedException(); | 93 ExecutionState m_state; |
19 | 94 |
20 public Exception LastError => throw new NotImplementedException(); | 95 |
96 protected RunnableComponent(bool initialized) { | |
97 State = initialized ? ExecutionState.Ready : ExecutionState.Created; | |
98 } | |
99 | |
100 public Task Completion { | |
101 get { return m_current.Task; } | |
102 } | |
103 | |
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; } | |
21 | 118 |
22 public event EventHandler<StateChangeEventArgs> StateChanged; | 119 public event EventHandler<StateChangeEventArgs> StateChanged; |
23 | 120 |
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> | |
24 public void Dispose() { | 130 public void Dispose() { |
25 lock(m_lock) { | 131 bool dispose = false; |
132 if (dispose) { | |
26 Dispose(true); | 133 Dispose(true); |
27 GC.SuppressFinalize(this); | 134 GC.SuppressFinalize(this); |
28 } | 135 } |
29 } | 136 } |
30 | 137 |
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> | |
31 protected virtual void Dispose(bool disposing) { | 148 protected virtual void Dispose(bool disposing) { |
32 if (disposing) { | 149 } |
33 Safe.Dispose(m_cts); | 150 |
34 } | 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 | |
164 /// <see cref="StartInternal(CancellationToken)"/> method. | |
165 /// </remarks> | |
166 protected virtual Task InitializeInternalAsync(CancellationToken ct) { | |
167 return Task.CompletedTask; | |
35 } | 168 } |
36 | 169 |
37 public void Start(CancellationToken ct) { | 170 public void Start(CancellationToken ct) { |
38 lock(m_lock) { | 171 var cookie = new object(); |
39 switch (State) | 172 if (MoveStart(cookie)) |
40 { | 173 ScheduleTask(StartInternal, ct, cookie); |
41 | 174 } |
42 default: | 175 |
43 throw new InvalidOperationException(); | 176 protected virtual Task StartInternal(CancellationToken ct) { |
44 } | 177 return Task.CompletedTask; |
45 } | |
46 } | 178 } |
47 | 179 |
48 public void Stop(CancellationToken ct) { | 180 public void Stop(CancellationToken ct) { |
49 throw new NotImplementedException(); | 181 var cookie = new object(); |
50 } | 182 if (MoveStop(cookie)) |
51 | 183 ScheduleTask(StopAsync, ct, cookie); |
52 protected virtual Task StartImpl(CancellationToken ct) { | 184 } |
53 | 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) { | |
54 return Task.CompletedTask; | 196 return Task.CompletedTask; |
55 } | 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; | |
246 } | |
247 } | |
248 } | |
249 | |
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 } | |
257 } | |
258 | |
259 | |
260 | |
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 } | |
269 } | |
270 | |
271 #endregion | |
56 } | 272 } |
57 } | 273 } |