comparison Implab/Promise.cs @ 11:6ec82bf68c8e promises

refactoring
author cin
date Tue, 05 Nov 2013 01:09:58 +0400
parents aa33d0bb8c0c
children e943453e5039
comparison
equal deleted inserted replaced
10:aa33d0bb8c0c 11:6ec82bf68c8e
5 using System.Threading; 5 using System.Threading;
6 6
7 namespace Implab { 7 namespace Implab {
8 8
9 public delegate void ErrorHandler(Exception e); 9 public delegate void ErrorHandler(Exception e);
10 10 public delegate T ErrorHandler<out T>(Exception e);
11 public delegate void ResultHandler<in T>(T result); 11 public delegate void ResultHandler<in T>(T result);
12 public delegate TNew ResultMapper<in TSrc, out TNew>(TSrc result); 12 public delegate TNew ResultMapper<in TSrc, out TNew>(TSrc result);
13 public delegate Promise<TNew> ChainedOperation<in TSrc, TNew>(TSrc result); 13 public delegate Promise<TNew> ChainedOperation<in TSrc, TNew>(TSrc result);
14 14
15 /// <summary> 15 /// <summary>
124 /// <returns><c>true</c> Операция была отменена, обработчики не будут вызваны.<c>false</c> отмена не возможна, поскольку обещание уже выполнено и обработчики отработали.</returns> 124 /// <returns><c>true</c> Операция была отменена, обработчики не будут вызваны.<c>false</c> отмена не возможна, поскольку обещание уже выполнено и обработчики отработали.</returns>
125 public bool Cancel() { 125 public bool Cancel() {
126 return Cancel(true); 126 return Cancel(true);
127 } 127 }
128 128
129 /// <summary>
130 /// Adds new handlers to this promise.
131 /// </summary>
132 /// <param name="success">The handler of the successfully completed operation.
133 /// This handler will recieve an operation result as a parameter.</param>
134 /// <param name="error">Handles an exception that may occur during the operation.</param>
135 /// <returns>The new promise chained to this one.</returns>
136 public Promise<T> Then(ResultHandler<T> success, ErrorHandler error) {
137 if (success == null && error == null)
138 return this;
139
140 var medium = new Promise<T>(this, true);
141
142 var handlerInfo = new ResultHandlerInfo();
143
144 if (success != null)
145 handlerInfo.resultHandler = x => {
146 success(x);
147 medium.Resolve(x);
148 };
149 else
150 handlerInfo.resultHandler = medium.Resolve;
151
152 if (error != null)
153 handlerInfo.errorHandler = x => {
154 try {
155 error(x);
156 } catch { }
157 medium.Reject(x);
158 };
159 else
160 handlerInfo.errorHandler = medium.Reject;
161
162 AddHandler(handlerInfo);
163
164 return medium;
165 }
166
167 /// <summary>
168 /// Adds new handlers to this promise.
169 /// </summary>
170 /// <param name="success">The handler of the successfully completed operation.
171 /// This handler will recieve an operation result as a parameter.</param>
172 /// <param name="error">Handles an exception that may occur during the operation and returns the value which will be used as the result of the operation.</param>
173 /// <returns>The new promise chained to this one.</returns>
174 public Promise<T> Then(ResultHandler<T> success, ErrorHandler<T> error) {
175 if (success == null && error == null)
176 return this;
177
178 var medium = new Promise<T>(this, true);
179
180 var handlerInfo = new ResultHandlerInfo();
181
182 if (success != null)
183 handlerInfo.resultHandler = x => {
184 success(x);
185 medium.Resolve(x);
186 };
187 else
188 handlerInfo.resultHandler = medium.Resolve;
189
190 if (error != null)
191 handlerInfo.errorHandler = x => {
192 try {
193 medium.Resolve(error(x));
194 } catch { }
195 medium.Reject(x);
196 };
197 else
198 handlerInfo.errorHandler = medium.Reject;
199
200 AddHandler(handlerInfo);
201
202 return medium;
203 }
204
205
206 public Promise<T> Then(ResultHandler<T> success) {
207 if (success == null)
208 return this;
209
210 var medium = new Promise<T>(this, true);
211
212 var handlerInfo = new ResultHandlerInfo();
213
214 if (success != null)
215 handlerInfo.resultHandler = x => {
216 success(x);
217 medium.Resolve(x);
218 };
219 else
220 handlerInfo.resultHandler = medium.Resolve;
221
222 handlerInfo.errorHandler = medium.Reject;
223
224 AddHandler(handlerInfo);
225
226 return medium;
227 }
228
229 public Promise<T> Error(ErrorHandler error) {
230 return Then(null, error);
231 }
232
233 /// <summary>
234 /// Handles error and allows to keep the promise.
235 /// </summary>
236 /// <remarks>
237 /// If the specified handler throws an exception, this exception will be used to reject the promise.
238 /// </remarks>
239 /// <param name="handler">The error handler which returns the result of the promise.</param>
240 /// <returns>New promise.</returns>
241 public Promise<T> Error(ErrorHandler<T> handler) {
242 if (handler == null)
243 return this;
244
245 var medium = new Promise<T>(this, true);
246
247 AddHandler(new ResultHandlerInfo {
248 errorHandler = e => {
249 try {
250 medium.Resolve(handler(e));
251 } catch (Exception e2) {
252 medium.Reject(e2);
253 }
254 }
255 });
256
257 return medium;
258 }
259
260 public Promise<T> Anyway(Action handler) {
261 if (handler == null)
262 return this;
263
264 var medium = new Promise<T>();
265
266 AddHandler(new ResultHandlerInfo {
267 resultHandler = x => {
268 // to avoid handler being called multiple times we handle exception by ourselfs
269 try {
270 handler();
271 medium.Resolve(x);
272 } catch (Exception e) {
273 medium.Reject(e);
274 }
275 },
276 errorHandler = x => {
277 try {
278 handler();
279 } catch { }
280 medium.Reject(x);
281 }
282 });
283
284 return medium;
285 }
286
287 /// <summary>
288 /// Позволяет преобразовать результат выполения операции к новому типу.
289 /// </summary>
290 /// <typeparam name="TNew">Новый тип результата.</typeparam>
291 /// <param name="mapper">Преобразование результата к новому типу.</param>
292 /// <param name="error">Обработчик ошибки. Данный обработчик получит
293 /// исключение возникшее при выполнении операции.</param>
294 /// <returns>Новое обещание, которое будет выполнено при выполнении исходного обещания.</returns>
295 public Promise<TNew> Map<TNew>(ResultMapper<T, TNew> mapper, ErrorHandler error) {
296 if (mapper == null)
297 throw new ArgumentNullException("mapper");
298
299 // создаем прицепленное обещание
300 var chained = new Promise<TNew>();
301
302 AddHandler(new ResultHandlerInfo() {
303 resultHandler = result => chained.Resolve(mapper(result)),
304 errorHandler = delegate(Exception e) {
305 if (error != null)
306 try {
307 error(e);
308 } catch { }
309 // в случае ошибки нужно передать исключение дальше по цепочке
310 chained.Reject(e);
311 }
312 });
313
314 return chained;
315 }
316
317 public Promise<TNew> Map<TNew>(ResultMapper<T, TNew> mapper) {
318 return Map(mapper, null);
319 }
320
321 /// <summary>
322 /// Сцепляет несколько аснхронных операций. Указанная асинхронная операция будет вызвана после
323 /// выполнения текущей, а результат текущей операции может быть использован для инициализации
324 /// новой операции.
325 /// </summary>
326 /// <typeparam name="TNew">Тип результата указанной асинхронной операции.</typeparam>
327 /// <param name="chained">Асинхронная операция, которая должна будет начаться после выполнения текущей.</param>
328 /// <param name="error">Обработчик ошибки. Данный обработчик получит
329 /// исключение возникшее при выполнении текуещй операции.</param>
330 /// <returns>Новое обещание, которое будет выполнено по окончанию указанной аснхронной операции.</returns>
331 public Promise<TNew> Chain<TNew>(ChainedOperation<T, TNew> chained, ErrorHandler error) {
332
333 // проблема в том, что на момент связывания еще не начата асинхронная операция, поэтому нужно
334 // создать посредника, к которому будут подвызяваться следующие обработчики.
335 // когда будет выполнена реальная асинхронная операция, она обратиться к посреднику, чтобы
336 // передать через него результаты работы.
337 var medium = new Promise<TNew>(this, true);
338
339 AddHandler(new ResultHandlerInfo {
340 resultHandler = delegate(T result) {
341 if (medium.State == PromiseState.Cancelled)
342 return;
343
344 var promise = chained(result);
345
346 // notify chained operation that it's not needed
347 medium.Cancelled(() => promise.Cancel());
348 promise.Then(
349 x => medium.Resolve(x),
350 e => medium.Reject(e)
351 );
352 },
353 errorHandler = delegate(Exception e) {
354 if (error != null)
355 error(e);
356 // в случае ошибки нужно передать исключение дальше по цепочке
357 medium.Reject(e);
358 }
359 });
360
361 return medium;
362 }
363
364 public Promise<TNew> Chain<TNew>(ChainedOperation<T, TNew> chained) {
365 return Chain(chained, null);
366 }
367
368 public Promise<T> Cancelled(Action handler) {
369 if (handler == null)
370 return this;
371 lock (m_lock) {
372 if (m_state == PromiseState.Unresolved)
373 m_cancelHandlers.AddLast(handler);
374 else if (m_state == PromiseState.Cancelled)
375 handler();
376 }
377 return this;
378 }
379
380 public void HandleCancelled(Action handler) {
381 Cancelled(handler);
382 }
383
384 /// <summary>
385 /// Дожидается отложенного обещания и в случае успеха, возвращает
386 /// его, результат, в противном случае бросает исключение.
387 /// </summary>
388 /// <remarks>
389 /// <para>
390 /// Если ожидание обещания было прервано по таймауту, это не значит,
391 /// что обещание было отменено или что-то в этом роде, это только
392 /// означает, что мы его не дождались, однако все зарегистрированные
393 /// обработчики, как были так остались и они будут вызваны, когда
394 /// обещание будет выполнено.
395 /// </para>
396 /// <para>
397 /// Такое поведение вполне оправдано поскольку таймаут может истечь
398 /// в тот момент, когда началась обработка цепочки обработчиков, и
399 /// к тому же текущее обещание может стоять в цепочке обещаний и его
400 /// отклонение может привести к непрогнозируемому результату.
401 /// </para>
402 /// </remarks>
403 /// <param name="timeout">Время ожидания</param>
404 /// <returns>Результат выполнения обещания</returns>
405 public T Join(int timeout) {
406 var evt = new ManualResetEvent(false);
407 Anyway(() => evt.Set());
408 Cancelled(() => evt.Set());
409
410 if (!evt.WaitOne(timeout, true))
411 throw new TimeoutException();
412
413 switch (State) {
414 case PromiseState.Resolved:
415 return m_result;
416 case PromiseState.Cancelled:
417 throw new OperationCanceledException();
418 case PromiseState.Rejected:
419 throw new TargetInvocationException(m_error);
420 default:
421 throw new ApplicationException(String.Format("Invalid promise state {0}", State));
422 }
423 }
424
425 public T Join() {
426 return Join(Timeout.Infinite);
427 }
428
429 void AddHandler(ResultHandlerInfo handler) {
430 bool invokeRequired = false;
431
432 lock (m_lock) {
433 m_childrenCount++;
434 if (m_state == PromiseState.Unresolved) {
435 m_resultHandlers.AddLast(handler);
436 } else
437 invokeRequired = true;
438 }
439
440 // обработчики не должны блокировать сам объект
441 if (invokeRequired)
442 InvokeHandler(handler);
443 }
444
445 void InvokeHandler(ResultHandlerInfo handler) {
446 switch (m_state) {
447 case PromiseState.Resolved:
448 try {
449 if (handler.resultHandler != null)
450 handler.resultHandler(m_result);
451 } catch (Exception e) {
452 try {
453 if (handler.errorHandler != null)
454 handler.errorHandler(e);
455 } catch { }
456 }
457 break;
458 case PromiseState.Rejected:
459 try {
460 if (handler.errorHandler != null)
461 handler.errorHandler(m_error);
462 } catch { }
463 break;
464 default:
465 // do nothing
466 return;
467 }
468 }
469
129 protected virtual void OnStateChanged() { 470 protected virtual void OnStateChanged() {
130 switch (m_state) { 471 switch (m_state) {
131 case PromiseState.Resolved: 472 case PromiseState.Resolved:
132 foreach (var resultHandlerInfo in m_resultHandlers) 473 foreach (var resultHandlerInfo in m_resultHandlers)
133 try { 474 try {
157 498
158 m_resultHandlers = null; 499 m_resultHandlers = null;
159 m_cancelHandlers = null; 500 m_cancelHandlers = null;
160 } 501 }
161 502
162 /// <summary>
163 /// Добавляет обработчики событий выполнения обещания.
164 /// </summary>
165 /// <param name="success">Обработчик успешного выполнения обещания.
166 /// Данному обработчику будет передан результат выполнения операции.</param>
167 /// <param name="error">Обработчик ошибки. Данный обработчик получит
168 /// исключение возникшее при выполнении операции.</param>
169 /// <returns>Само обещание</returns>
170 public Promise<T> Then(ResultHandler<T> success, ErrorHandler error) {
171 if (success == null && error == null)
172 return this;
173
174 var medium = new Promise<T>();
175
176 var handlerInfo = new ResultHandlerInfo();
177
178 if (success != null)
179 handlerInfo.resultHandler = x => {
180 success(x);
181 medium.Resolve(x);
182 };
183 else
184 handlerInfo.resultHandler = medium.Resolve;
185
186 if (error != null)
187 handlerInfo.errorHandler = x => {
188 try {
189 error(x);
190 } catch { }
191 medium.Reject(x);
192 };
193 else
194 handlerInfo.errorHandler = medium.Reject;
195
196 AddHandler(handlerInfo);
197
198 return medium;
199 }
200
201 public Promise<T> Then(ResultHandler<T> success) {
202 return Then(success, null);
203 }
204
205 public Promise<T> Error(ErrorHandler error) {
206 return Then(null, error);
207 }
208
209 public Promise<T> Anyway(Action handler) {
210 if (handler == null)
211 return this;
212
213 var medium = new Promise<T>();
214
215 AddHandler(new ResultHandlerInfo {
216 resultHandler = x => {
217 // to avoid handler being called multiple times we handle exception by ourselfs
218 try {
219 handler();
220 medium.Resolve(x);
221 } catch (Exception e) {
222 medium.Reject(e);
223 }
224 },
225 errorHandler = x => {
226 try {
227 handler();
228 } catch { }
229 medium.Reject(x);
230 }
231 });
232
233 return medium;
234 }
235
236 /// <summary>
237 /// Позволяет преобразовать результат выполения операции к новому типу.
238 /// </summary>
239 /// <typeparam name="TNew">Новый тип результата.</typeparam>
240 /// <param name="mapper">Преобразование результата к новому типу.</param>
241 /// <param name="error">Обработчик ошибки. Данный обработчик получит
242 /// исключение возникшее при выполнении операции.</param>
243 /// <returns>Новое обещание, которое будет выполнено при выполнении исходного обещания.</returns>
244 public Promise<TNew> Map<TNew>(ResultMapper<T, TNew> mapper, ErrorHandler error) {
245 if (mapper == null)
246 throw new ArgumentNullException("mapper");
247
248 // создаем прицепленное обещание
249 var chained = new Promise<TNew>();
250
251 AddHandler(new ResultHandlerInfo() {
252 resultHandler = result => chained.Resolve(mapper(result)),
253 errorHandler = delegate(Exception e) {
254 if (error != null)
255 try {
256 error(e);
257 } catch { }
258 // в случае ошибки нужно передать исключение дальше по цепочке
259 chained.Reject(e);
260 }
261 });
262
263 return chained;
264 }
265
266 public Promise<TNew> Map<TNew>(ResultMapper<T, TNew> mapper) {
267 return Map(mapper, null);
268 }
269
270 /// <summary>
271 /// Сцепляет несколько аснхронных операций. Указанная асинхронная операция будет вызвана после
272 /// выполнения текущей, а результат текущей операции может быть использован для инициализации
273 /// новой операции.
274 /// </summary>
275 /// <typeparam name="TNew">Тип результата указанной асинхронной операции.</typeparam>
276 /// <param name="chained">Асинхронная операция, которая должна будет начаться после выполнения текущей.</param>
277 /// <param name="error">Обработчик ошибки. Данный обработчик получит
278 /// исключение возникшее при выполнении текуещй операции.</param>
279 /// <returns>Новое обещание, которое будет выполнено по окончанию указанной аснхронной операции.</returns>
280 public Promise<TNew> Chain<TNew>(ChainedOperation<T, TNew> chained, ErrorHandler error) {
281
282 // проблема в том, что на момент связывания еще не начата асинхронная операция, поэтому нужно
283 // создать посредника, к которому будут подвызяваться следующие обработчики.
284 // когда будет выполнена реальная асинхронная операция, она обратиться к посреднику, чтобы
285 // передать через него результаты работы.
286 var medium = new Promise<TNew>(this, true);
287
288 AddHandler(new ResultHandlerInfo {
289 resultHandler = delegate(T result) {
290 if (medium.State == PromiseState.Cancelled)
291 return;
292
293 var promise = chained(result);
294
295 // notify chained operation that it's not needed
296 medium.Cancelled(() => promise.Cancel());
297 promise.Then(
298 medium.Resolve,
299 medium.Reject
300 );
301 },
302 errorHandler = delegate(Exception e) {
303 if (error != null)
304 error(e);
305 // в случае ошибки нужно передать исключение дальше по цепочке
306 medium.Reject(e);
307 }
308 });
309
310 return medium;
311 }
312
313 public Promise<TNew> Chain<TNew>(ChainedOperation<T, TNew> chained) {
314 return Chain(chained, null);
315 }
316
317 public Promise<T> Cancelled(Action handler) {
318 if (handler == null)
319 return this;
320 lock (m_lock) {
321 if (m_state == PromiseState.Unresolved)
322 m_cancelHandlers.AddLast(handler);
323 else if (m_state == PromiseState.Cancelled)
324 handler();
325 }
326 return this;
327 }
328
329 public void HandleCancelled(Action handler) {
330 Cancelled(handler);
331 }
332
333 /// <summary>
334 /// Дожидается отложенного обещания и в случае успеха, возвращает
335 /// его, результат, в противном случае бросает исключение.
336 /// </summary>
337 /// <remarks>
338 /// <para>
339 /// Если ожидание обещания было прервано по таймауту, это не значит,
340 /// что обещание было отменено или что-то в этом роде, это только
341 /// означает, что мы его не дождались, однако все зарегистрированные
342 /// обработчики, как были так остались и они будут вызваны, когда
343 /// обещание будет выполнено.
344 /// </para>
345 /// <para>
346 /// Такое поведение вполне оправдано поскольку таймаут может истечь
347 /// в тот момент, когда началась обработка цепочки обработчиков, и
348 /// к тому же текущее обещание может стоять в цепочке обещаний и его
349 /// отклонение может привести к непрогнозируемому результату.
350 /// </para>
351 /// </remarks>
352 /// <param name="timeout">Время ожидания</param>
353 /// <returns>Результат выполнения обещания</returns>
354 public T Join(int timeout) {
355 var evt = new ManualResetEvent(false);
356 Anyway(() => evt.Set());
357 Cancelled(() => evt.Set());
358
359 if (!evt.WaitOne(timeout, true))
360 throw new TimeoutException();
361
362 switch (State) {
363 case PromiseState.Resolved:
364 return m_result;
365 case PromiseState.Cancelled:
366 throw new OperationCanceledException();
367 case PromiseState.Rejected:
368 throw new TargetInvocationException(m_error);
369 default:
370 throw new ApplicationException(String.Format("Invalid promise state {0}", State));
371 }
372 }
373
374 public T Join() {
375 return Join(Timeout.Infinite);
376 }
377
378 void AddHandler(ResultHandlerInfo handler) {
379 bool invokeRequired = false;
380
381 lock (m_lock) {
382 m_childrenCount++;
383 if (m_state == PromiseState.Unresolved) {
384 m_resultHandlers.AddLast(handler);
385 } else
386 invokeRequired = true;
387 }
388
389 // обработчики не должны блокировать сам объект
390 if (invokeRequired)
391 InvokeHandler(handler);
392 }
393
394 void InvokeHandler(ResultHandlerInfo handler) {
395 switch (m_state) {
396 case PromiseState.Resolved:
397 try {
398 if (handler.resultHandler != null)
399 handler.resultHandler(m_result);
400 } catch (Exception e) {
401 try {
402 if (handler.errorHandler != null)
403 handler.errorHandler(e);
404 } catch { }
405 }
406 break;
407 case PromiseState.Rejected:
408 try {
409 if (handler.errorHandler != null)
410 handler.errorHandler(m_error);
411 } catch { }
412 break;
413 default:
414 // do nothing
415 return;
416 }
417 }
418
419 503
420 504
421 public bool IsExclusive { 505 public bool IsExclusive {
422 get { 506 get {
423 lock (m_lock) { 507 lock (m_lock) {
432 return m_state; 516 return m_state;
433 } 517 }
434 } 518 }
435 } 519 }
436 520
437 public bool Cancel(bool dependencies) { 521 protected bool Cancel(bool dependencies) {
438 bool result; 522 bool result;
439 523
440 lock (m_lock) { 524 lock (m_lock) {
441 if (m_state == PromiseState.Unresolved) { 525 if (m_state == PromiseState.Unresolved) {
442 m_state = PromiseState.Cancelled; 526 m_state = PromiseState.Cancelled;
448 532
449 if (result) 533 if (result)
450 OnStateChanged(); 534 OnStateChanged();
451 535
452 if (dependencies && m_parent != null && m_parent.IsExclusive) { 536 if (dependencies && m_parent != null && m_parent.IsExclusive) {
453 m_parent.Cancel(true); 537 m_parent.Cancel();
454 } 538 }
455 539
456 return result; 540 return result;
457 } 541 }
458 } 542 }