Mercurial > pub > ImplabNet
comparison Implab/Promise.cs @ 0:279591fb4df3
initial commit
promises async model
author | user@factory.site.local |
---|---|
date | Fri, 23 Aug 2013 04:38:46 +0400 |
parents | |
children | 6fb3b01ee971 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:279591fb4df3 |
---|---|
1 using System; | |
2 using System.Collections.Generic; | |
3 using System.Linq; | |
4 using System.Reflection; | |
5 using System.Text; | |
6 using System.Diagnostics; | |
7 using System.Threading; | |
8 | |
9 namespace Implab { | |
10 | |
11 public delegate void ErrorHandler(Exception e); | |
12 | |
13 public delegate void ResultHandler<T>(T result); | |
14 public delegate TNew ResultMapper<TSrc,TNew>(TSrc result); | |
15 public delegate Promise<TNew> ChainedOperation<TSrc,TNew>(TSrc result); | |
16 | |
17 /// <summary> | |
18 /// Класс для асинхронного получения результатов. Так называемое "обещание". | |
19 /// </summary> | |
20 /// <typeparam name="T">Тип получаемого результата</typeparam> | |
21 /// <remarks> | |
22 /// <para>Сервис при обращении к его методу дает обещаиние о выполнении операции, | |
23 /// клиент получив такое обещание может установить ряд обратных вызово для получения | |
24 /// событий выполнения обещания, тоесть завершения операции и предоставлении результатов.</para> | |
25 /// <para> | |
26 /// Обещение может быть как выполнено, так и выполнено с ошибкой. Для подписки на | |
27 /// данные события клиент должен использовать методы <c>Then</c>. | |
28 /// </para> | |
29 /// <para> | |
30 /// Сервис, в свою очередь, по окончанию выполнения операции (возможно с ошибкой), | |
31 /// использует методы <c>Resolve</c> либо <c>Reject</c> для оповещения клиетна о | |
32 /// выполнении обещания. | |
33 /// </para> | |
34 /// <para> | |
35 /// Если сервер успел выполнить обещание еще до того, как клиент на него подписался, | |
36 /// то в момент подписки клиента будут вызваны соответсвующие события в синхронном | |
37 /// режиме и клиент будет оповещен в любом случае. Иначе, обработчики добавляются в | |
38 /// список в порядке подписания и в этом же порядке они будут вызваны при выполнении | |
39 /// обещания. | |
40 /// </para> | |
41 /// <para> | |
42 /// Обрабатывая результаты обещания можно преобразовывать результаты либо инициировать | |
43 /// связанные асинхронные операции, которые также возвращают обещания. Для этого следует | |
44 /// использовать соответствующую форму методе <c>Then</c>. | |
45 /// </para> | |
46 /// <para> | |
47 /// Также хорошим правилом является то, что <c>Resolve</c> и <c>Reject</c> должен вызывать | |
48 /// только инициатор обещания иначе могут возникнуть противоречия. | |
49 /// </para> | |
50 /// </remarks> | |
51 public class Promise<T> { | |
52 | |
53 struct ResultHandlerInfo { | |
54 public ResultHandler<T> resultHandler; | |
55 public ErrorHandler errorHandler; | |
56 } | |
57 | |
58 enum State { | |
59 Unresolved, | |
60 Resolving, | |
61 Resolved, | |
62 Cancelled | |
63 } | |
64 | |
65 LinkedList<ResultHandlerInfo> m_handlersChain = new LinkedList<ResultHandlerInfo>(); | |
66 State m_state; | |
67 bool m_cancellable; | |
68 T m_result; | |
69 Exception m_error; | |
70 | |
71 public Promise() { | |
72 m_cancellable = true; | |
73 } | |
74 | |
75 /// <summary> | |
76 /// Событие, возникающее при отмене асинхронной операции. | |
77 /// </summary> | |
78 /// <description> | |
79 /// Как правило используется для оповещения объекта, выполняющего асинхронную операцию, о том, что ее следует отменить. | |
80 /// </description> | |
81 public event EventHandler Cancelled; | |
82 | |
83 /// <summary> | |
84 /// Выполняет обещание, сообщая об успешном выполнении. | |
85 /// </summary> | |
86 /// <param name="result">Результат выполнения.</param> | |
87 /// <exception cref="InvalidOperationException">Данное обещание уже выполнено</exception> | |
88 public void Resolve(T result) { | |
89 lock (this) { | |
90 if (m_state == State.Cancelled) | |
91 return; | |
92 if (m_state != State.Unresolved) | |
93 throw new InvalidOperationException("The promise is already resolved"); | |
94 m_result = result; | |
95 m_state = State.Resolving; | |
96 } | |
97 | |
98 ResultHandlerInfo handler; | |
99 while (FetchNextHandler(out handler)) | |
100 InvokeHandler(handler); | |
101 } | |
102 | |
103 /// <summary> | |
104 /// Выполняет обещание, сообщая об ошибке | |
105 /// </summary> | |
106 /// <param name="error">Исключение возникшее при выполнении операции</param> | |
107 /// <exception cref="InvalidOperationException">Данное обещание уже выполнено</exception> | |
108 public void Reject(Exception error) { | |
109 lock (this) { | |
110 if (m_state == State.Cancelled) | |
111 return; | |
112 if (m_state != State.Unresolved) | |
113 throw new InvalidOperationException("The promise is already resolved"); | |
114 m_error = error; | |
115 m_state = State.Resolving; | |
116 } | |
117 | |
118 ResultHandlerInfo handler; | |
119 while (FetchNextHandler(out handler)) | |
120 InvokeHandler(handler); | |
121 } | |
122 | |
123 /// <summary> | |
124 /// Отменяет операцию, если это возможно. | |
125 /// </summary> | |
126 /// <returns><c>true</c> Операция была отменена, обработчики не будут вызваны.<c>false</c> отмена не возможна, поскольку обещание уже выполнено и обработчики отработали.</returns> | |
127 public bool Cancel() { | |
128 lock(this) { | |
129 if (m_state == State.Unresolved && m_cancellable) { | |
130 m_state = State.Cancelled; | |
131 return true; | |
132 } else | |
133 return false; | |
134 } | |
135 } | |
136 | |
137 /// <summary> | |
138 /// Добавляет обработчики событий выполнения обещания. | |
139 /// </summary> | |
140 /// <param name="success">Обработчик успешного выполнения обещания. | |
141 /// Данному обработчику будет передан результат выполнения операции.</param> | |
142 /// <param name="error">Обработчик ошибки. Данный обработчик получит | |
143 /// исключение возникшее при выполнении операции.</param> | |
144 /// <returns>Само обещание</returns> | |
145 public Promise<T> Then(ResultHandler<T> success, ErrorHandler error) { | |
146 if (success == null && error == null) | |
147 return this; | |
148 | |
149 AddHandler(new ResultHandlerInfo() { | |
150 resultHandler = success, | |
151 errorHandler = error | |
152 }); | |
153 | |
154 return this; | |
155 } | |
156 | |
157 public Promise<T> Then(ResultHandler<T> success) { | |
158 return Then (success, null); | |
159 } | |
160 | |
161 public Promise<T> Anyway(Action handler) { | |
162 if (handler == null) | |
163 return this; | |
164 AddHandler(new ResultHandlerInfo { | |
165 resultHandler = x => handler(), | |
166 errorHandler = x => handler() | |
167 }); | |
168 | |
169 return this; | |
170 } | |
171 | |
172 /// <summary> | |
173 /// Позволяет преобразовать результат выполения операции к новому типу. | |
174 /// </summary> | |
175 /// <typeparam name="TNew">Новый тип результата.</typeparam> | |
176 /// <param name="mapper">Преобразование результата к новому типу.</param> | |
177 /// <param name="error">Обработчик ошибки. Данный обработчик получит | |
178 /// исключение возникшее при выполнении операции.</param> | |
179 /// <returns>Новое обещание, которое будет выполнено при выполнении исходного обещания.</returns> | |
180 public Promise<TNew> Map<TNew>(ResultMapper<T, TNew> mapper, ErrorHandler error) { | |
181 if (mapper == null) | |
182 throw new ArgumentNullException("mapper"); | |
183 | |
184 // создаем прицепленное обещание | |
185 Promise<TNew> chained = new Promise<TNew>(); | |
186 | |
187 AddHandler(new ResultHandlerInfo() { | |
188 resultHandler = delegate(T result) { | |
189 try { | |
190 // если преобразование выдаст исключение, то сработает reject сцепленного deferred | |
191 chained.Resolve(mapper(result)); | |
192 } catch (Exception e) { | |
193 chained.Reject(e); | |
194 } | |
195 }, | |
196 errorHandler = delegate(Exception e) { | |
197 if (error != null) | |
198 error(e); | |
199 // в случае ошибки нужно передать исключение дальше по цепочке | |
200 chained.Reject(e); | |
201 } | |
202 }); | |
203 | |
204 return chained; | |
205 } | |
206 | |
207 public Promise<TNew> Map<TNew>(ResultMapper<T, TNew> mapper) { | |
208 return Map (mapper, null); | |
209 } | |
210 | |
211 /// <summary> | |
212 /// Сцепляет несколько аснхронных операций. Указанная асинхронная операция будет вызвана после | |
213 /// выполнения текущей, а результат текущей операции может быть использован для инициализации | |
214 /// новой операции. | |
215 /// </summary> | |
216 /// <typeparam name="TNew">Тип результата указанной асинхронной операции.</typeparam> | |
217 /// <param name="chained">Асинхронная операция, которая должна будет начаться после выполнения текущей.</param> | |
218 /// <param name="error">Обработчик ошибки. Данный обработчик получит | |
219 /// исключение возникшее при выполнении текуещй операции.</param> | |
220 /// <returns>Новое обещание, которое будет выполнено по окончанию указанной аснхронной операции.</returns> | |
221 public Promise<TNew> Chain<TNew>(ChainedOperation<T, TNew> chained, ErrorHandler error) { | |
222 | |
223 // проблема в том, что на момент связывания еще не начата асинхронная операция, поэтому нужно | |
224 // создать посредника, к которому будут подвызяваться следующие обработчики. | |
225 // когда будет выполнена реальная асинхронная операция, она обратиться к посреднику, чтобы | |
226 // передать через него результаты работы. | |
227 Promise<TNew> medium = new Promise<TNew>(); | |
228 | |
229 AddHandler(new ResultHandlerInfo() { | |
230 resultHandler = delegate(T result) { | |
231 try { | |
232 chained(result).Then( | |
233 x => medium.Resolve(x), | |
234 e => medium.Reject(e) | |
235 ); | |
236 } catch(Exception e) { | |
237 // если сцепленное действие выдало исключение вместо обещания, то передаем ошибку по цепочке | |
238 medium.Reject(e); | |
239 } | |
240 }, | |
241 errorHandler = delegate(Exception e) { | |
242 if (error != null) | |
243 error(e); | |
244 // в случае ошибки нужно передать исключение дальше по цепочке | |
245 medium.Reject(e); | |
246 } | |
247 }); | |
248 | |
249 return medium; | |
250 } | |
251 | |
252 public Promise<TNew> Chain<TNew>(ChainedOperation<T, TNew> chained) { | |
253 return Chain (chained, null); | |
254 } | |
255 | |
256 /// <summary> | |
257 /// Дожидается отложенного обещания и в случае успеха, возвращает | |
258 /// его, результат, в противном случае бросает исключение. | |
259 /// </summary> | |
260 /// <remarks> | |
261 /// <para> | |
262 /// Если ожидание обещания было прервано по таймауту, это не значит, | |
263 /// что обещание было отменено или что-то в этом роде, это только | |
264 /// означает, что мы его не дождались, однако все зарегистрированные | |
265 /// обработчики, как были так остались и они будут вызваны, когда | |
266 /// обещание будет выполнено. | |
267 /// </para> | |
268 /// <para> | |
269 /// Такое поведение вполне оправдано поскольку таймаут может истечь | |
270 /// в тот момент, когда началась обработка цепочки обработчиков, и | |
271 /// к тому же текущее обещание может стоять в цепочке обещаний и его | |
272 /// отклонение может привести к непрогнозируемому результату. | |
273 /// </para> | |
274 /// </remarks> | |
275 /// <param name="timeout">Время ожидания</param> | |
276 /// <returns>Результат выполнения обещания</returns> | |
277 public T Join(int timeout) { | |
278 ManualResetEvent evt = new ManualResetEvent(false); | |
279 Anyway(() => evt.Set()); | |
280 | |
281 if (!evt.WaitOne(timeout, true)) | |
282 throw new TimeoutException(); | |
283 | |
284 if (m_error != null) | |
285 throw new TargetInvocationException( m_error ); | |
286 else | |
287 return m_result; | |
288 } | |
289 | |
290 public T Join() { | |
291 return Join(Timeout.Infinite); | |
292 } | |
293 | |
294 /// <summary> | |
295 /// Данный метод последовательно извлекает обработчики обещания и когда | |
296 /// их больше не осталось - ставит состояние "разрешено". | |
297 /// </summary> | |
298 /// <param name="handler">Информация об обработчике</param> | |
299 /// <returns>Признак того, что еще остались обработчики в очереди</returns> | |
300 bool FetchNextHandler(out ResultHandlerInfo handler) { | |
301 handler = default(ResultHandlerInfo); | |
302 | |
303 lock (this) { | |
304 Debug.Assert(m_state == State.Resolving); | |
305 | |
306 if (m_handlersChain.Count > 0) { | |
307 handler = m_handlersChain.First.Value; | |
308 m_handlersChain.RemoveFirst(); | |
309 return true; | |
310 } else { | |
311 m_state = State.Resolved; | |
312 return false; | |
313 } | |
314 } | |
315 } | |
316 | |
317 void AddHandler(ResultHandlerInfo handler) { | |
318 bool invokeRequired = false; | |
319 | |
320 lock (this) { | |
321 if (m_state != State.Resolved) | |
322 m_handlersChain.AddLast(handler); | |
323 else | |
324 invokeRequired = true; | |
325 } | |
326 | |
327 // обработчики не должны блокировать сам объект | |
328 if (invokeRequired) | |
329 InvokeHandler(handler); | |
330 } | |
331 | |
332 void InvokeHandler(ResultHandlerInfo handler) { | |
333 if (m_error == null) { | |
334 try { | |
335 if (handler.resultHandler != null) | |
336 handler.resultHandler(m_result); | |
337 } catch { } | |
338 } | |
339 | |
340 if (m_error != null) { | |
341 try { | |
342 if (handler.errorHandler !=null) | |
343 handler.errorHandler(m_error); | |
344 } catch { } | |
345 } | |
346 } | |
347 | |
348 | |
349 } | |
350 } |