Mercurial > pub > ImplabNet
diff Implab/PromiseT.cs @ 119:2573b562e328 v2
Promises rewritten, added improved version of AsyncQueue
author | cin |
---|---|
date | Sun, 11 Jan 2015 19:13:02 +0300 |
parents | |
children | 671f60cd0250 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab/PromiseT.cs Sun Jan 11 19:13:02 2015 +0300 @@ -0,0 +1,621 @@ +using System; +using System.Diagnostics; + +namespace Implab { + + /// <summary> + /// Класс для асинхронного получения результатов. Так называемое "обещание". + /// </summary> + /// <typeparam name="T">Тип получаемого результата</typeparam> + /// <remarks> + /// <para>Сервис при обращении к его методу дает обещаиние о выполнении операции, + /// клиент получив такое обещание может установить ряд обратных вызово для получения + /// событий выполнения обещания, тоесть завершения операции и предоставлении результатов.</para> + /// <para> + /// Обещение может быть как выполнено, так и выполнено с ошибкой. Для подписки на + /// данные события клиент должен использовать методы <c>Then</c>. + /// </para> + /// <para> + /// Сервис, в свою очередь, по окончанию выполнения операции (возможно с ошибкой), + /// использует методы <c>Resolve</c> либо <c>Reject</c> для оповещения клиетна о + /// выполнении обещания. + /// </para> + /// <para> + /// Если сервер успел выполнить обещание еще до того, как клиент на него подписался, + /// то в момент подписки клиента будут вызваны соответсвующие события в синхронном + /// режиме и клиент будет оповещен в любом случае. Иначе, обработчики добавляются в + /// список в порядке подписания и в этом же порядке они будут вызваны при выполнении + /// обещания. + /// </para> + /// <para> + /// Обрабатывая результаты обещания можно преобразовывать результаты либо инициировать + /// связанные асинхронные операции, которые также возвращают обещания. Для этого следует + /// использовать соответствующую форму методе <c>Then</c>. + /// </para> + /// <para> + /// Также хорошим правилом является то, что <c>Resolve</c> и <c>Reject</c> должен вызывать + /// только инициатор обещания иначе могут возникнуть противоречия. + /// </para> + /// </remarks> + public class Promise<T> : AbstractPromise<IDeferred<T>>, IPromise<T>, IDeferred<T> { + + class StubDeferred : IDeferred<T> { + public static readonly StubDeferred instance = new StubDeferred(); + + StubDeferred() { + } + + #region IDeferred implementation + + public void Resolve(T value) { + } + + public void Reject(Exception error) { + } + + #endregion + + #region ICancellable implementation + + public void Cancel() { + } + + #endregion + + + } + + class RemapDescriptor<T2> : IDeferred<T> { + readonly Func<T,T2> m_remap; + readonly Func<Exception,T2> m_failed; + readonly Func<T2> m_cancel; + readonly IDeferred<T2> m_deferred; + + public RemapDescriptor(Func<T,T2> remap, Func<Exception,T2> failed, Func<T2> cancel, IDeferred<T2> deferred ) { + Debug.Assert(deferred != null); + m_remap = remap; + m_failed = failed; + m_cancel = cancel; + m_deferred = deferred; + } + + + + #region IDeferred implementation + + public void Resolve(T value) { + if (m_remap != null) { + try { + m_deferred.Resolve(m_remap(value)); + } catch (Exception ex) { + Reject(ex); + } + } + } + + public void Reject(Exception error) { + if (m_failed != null) { + try { + m_deferred.Resolve(m_failed(error)); + } catch (Exception ex) { + m_deferred.Reject(ex); + } + } else { + m_deferred.Reject(error); + } + } + + + #endregion + + #region ICancellable implementation + + public void Cancel() { + if (m_cancel != null) { + try { + m_deferred.Resolve(m_cancel()); + } catch (Exception ex) { + Reject(ex); + } + } else { + m_deferred.Cancel(); + } + } + + #endregion + } + + class ListenerDescriptor : IDeferred<T> { + readonly Action m_handler; + readonly PromiseEventType m_events; + + public ListenerDescriptor(Action handler, PromiseEventType events) { + Debug.Assert(handler != null); + + m_handler = handler; + m_events = events; + } + + #region IDeferred implementation + + public void Resolve(T value) { + if (m_events.HasFlag(PromiseEventType.Success)) { + try { + m_handler(); + // Analysis disable once EmptyGeneralCatchClause + } catch { + } + } + } + + public void Reject(Exception error) { + if (m_events.HasFlag(PromiseEventType.Error)){ + try { + m_handler(); + // Analysis disable once EmptyGeneralCatchClause + } catch { + } + } + } + + #endregion + + #region ICancellable implementation + + public void Cancel() { + if (m_events.HasFlag(PromiseEventType.Cancelled)){ + try { + m_handler(); + // Analysis disable once EmptyGeneralCatchClause + } catch { + } + } + } + + #endregion + } + + class ValueEventDescriptor : IDeferred<T> { + readonly Action<T> m_success; + readonly Action<Exception> m_failed; + readonly Action m_cancelled; + readonly IDeferred<T> m_deferred; + + public ValueEventDescriptor(Action<T> success, Action<Exception> failed, Action cancelled, IDeferred<T> deferred) { + Debug.Assert(deferred != null); + + m_success = success; + m_failed = failed; + m_cancelled = cancelled; + m_deferred = deferred; + } + + #region IDeferred implementation + + public void Resolve(T value) { + if (m_success != null) { + try { + m_success(value); + m_deferred.Resolve(value); + } catch (Exception ex) { + Reject(ex); + } + } + } + + public void Reject(Exception error) { + if (m_failed != null) { + try { + m_failed(error); + m_deferred.Resolve(default(T)); + } catch(Exception ex) { + m_deferred.Reject(ex); + } + } else { + m_deferred.Reject(error); + } + } + + #endregion + + #region ICancellable implementation + + public void Cancel() { + if (m_cancelled != null) { + try { + m_cancelled(); + m_deferred.Resolve(default(T)); + } catch(Exception ex) { + Reject(ex); + } + } else { + m_deferred.Cancel(); + } + } + + #endregion + } + + public class EventDescriptor : IDeferred<T> { + readonly Action m_success; + readonly Action<Exception> m_failed; + readonly Action m_cancelled; + readonly IDeferred<T> m_deferred; + + public EventDescriptor(Action success, Action<Exception> failed, Action cancelled, IDeferred<T> deferred) { + Debug.Assert(deferred != null); + + m_success = success; + m_failed = failed; + m_cancelled = cancelled; + m_deferred = deferred; + } + + #region IDeferred implementation + + public void Resolve(T value) { + if (m_success != null) { + try { + m_success(); + m_deferred.Resolve(value); + } catch (Exception ex) { + Reject(ex); + } + } + } + + public void Reject(Exception error) { + if (m_failed != null) { + try { + m_failed(error); + m_deferred.Resolve(default(T)); + }catch (Exception ex) + { + m_deferred.Reject(ex); + } + } else { + m_deferred.Reject(error); + } + + } + + #endregion + + #region ICancellable implementation + + public void Cancel() { + if (m_cancelled != null) { + try { + m_cancelled(); + m_deferred.Resolve(default(T)); + } catch (Exception ex) { + Reject(ex); + } + } else { + m_deferred.Cancel(); + } + } + + #endregion + } + + T m_result; + + public virtual void Resolve(T value) { + BeginSetResult(); + m_result = value; + EndSetResult(); + } + + public void Reject(Exception error) { + SetError(error); + } + + public Type PromiseType { + get { + return typeof(T); + } + } + + public new T Join() { + WaitResult(-1); + return m_result; + } + public new T Join(int timeout) { + WaitResult(timeout); + return m_result; + } + + public IPromise<T> On(Action<T> success, Action<Exception> error, Action cancel) { + AddHandler(new ValueEventDescriptor(success, error, cancel, StubDeferred.instance)); + return this; + } + + public IPromise<T> On(Action<T> success, Action<Exception> error) { + AddHandler(new ValueEventDescriptor(success, error, null, StubDeferred.instance)); + return this; + } + + public IPromise<T> On(Action<T> success) { + AddHandler(new ValueEventDescriptor(success, null, null, StubDeferred.instance)); + return this; + } + + public IPromise<T> On(Action handler, PromiseEventType events) { + Listen(events, handler); + return this; + } + + public IPromise<T2> Then<T2>(Func<T, T2> mapper, Func<Exception, T2> error, Func<T2> cancel) { + var promise = new Promise<T2>(); + AddHandler(new RemapDescriptor<T2>(mapper, error, cancel, promise)); + return promise; + } + + public IPromise<T2> Then<T2>(Func<T, T2> mapper, Func<Exception, T2> error) { + var promise = new Promise<T2>(); + AddHandler(new RemapDescriptor<T2>(mapper, error, null, promise)); + return promise; + } + + public IPromise<T2> Then<T2>(Func<T, T2> mapper) { + var promise = new Promise<T2>(); + AddHandler(new RemapDescriptor<T2>(mapper, null, null, promise)); + return promise; + } + + public IPromise<T2> Chain<T2>(Func<T, IPromise<T2>> chained, Func<Exception, IPromise<T2>> error, Func<IPromise<T2>> cancel) { + // this promise will be resolved when an asyc operation is started + var promise = new Promise<IPromise<T2>>(); + + AddHandler(new RemapDescriptor<IPromise<T2>>( + chained, + error, + cancel, + promise + )); + + var medium = new Promise<T2>(); + + if (chained != null) + medium.On(Cancel, PromiseEventType.Cancelled); + + // we need to connect started async operation with the medium + // if the async operation hasn't been started by the some reason + // report is to the medium + promise.On( + result => ConnectPromise<T2>(result, medium), + medium.Reject, + medium.Cancel + ); + + return medium; + } + + static void ConnectPromise<T2>(IPromise<T2> result, Promise<T2> medium) { + if (result != null) { + result.On( + medium.Resolve, + medium.Reject, + () => medium.Reject(new OperationCanceledException()) + ); + medium.On(result.Cancel, PromiseEventType.Cancelled); + } else { + medium.Reject( + new NullReferenceException( + "The chained asynchronous operation returned" + + " 'null' where the promise instance is expected" + ) + ); + } + } + + public IPromise<T2> Chain<T2>(Func<T, IPromise<T2>> chained, Func<Exception, IPromise<T2>> error) { + return Chain(chained, error, null); + } + + public IPromise<T2> Chain<T2>(Func<T, IPromise<T2>> chained) { + return Chain(chained, null, null); + } + + public IPromise<T2> Error<T2>(Func<Exception, T2> error) { + var promise = new Promise<T2>(); + if (error != null) + On( + (Action<T>)null, + ex => { + try { + promise.Resolve(error(ex)); + } catch (Exception ex2) { + promise.Reject(ex2); + } + } + ); + else + Listen(PromiseEventType.Error, () => promise.Resolve(default(T2))); + return promise; + } + + public IPromise<T2> Cancelled<T2>(Func<T2> handler) { + var promise = new Promise<T2>(); + if (handler != null) + On( + (Action<T>)null, + null, + () => { + try { + promise.Resolve(handler()); + } catch (Exception ex) { + promise.Reject(ex); + } + }); + else + Listen(PromiseEventType.Cancelled, () => promise.Resolve(default(T2))); + return promise; + } + + public IPromise Then(Action success, Action<Exception> error, Action cancel) { + var promise = new Promise<T>(); + if (success != null) + promise.On(Cancel, PromiseEventType.Cancelled); + + AddHandler(new EventDescriptor(success, error, cancel, promise)); + + return promise; + } + + public IPromise Then(Action success, Action<Exception> error) { + return Then(success, error, null); + } + + public IPromise Then(Action success) { + return Then(success, null, null); + } + + public IPromise Chain(Func<IPromise> chained, Func<Exception, IPromise> error, Func<IPromise> cancel) { + var promise = new Promise<IPromise>(); + + AddHandler( + new RemapDescriptor<IPromise>( + x => chained(), + error, + cancel, + promise + ) + ); + + var medium = new Promise(); + if (chained != null) + medium.On(Cancel, PromiseEventType.Cancelled); + + promise.On( + result => ConnectPromise(result, medium), + medium.Reject, + medium.Cancel + ); + + return medium; + } + + static void ConnectPromise(IPromise result, Promise medium) { + if (result != null) { + result.On( + medium.Resolve, + medium.Reject, + () => medium.Reject(new OperationCanceledException()) + ); + medium.On(result.Cancel, PromiseEventType.Cancelled); + } else { + medium.Reject( + new NullReferenceException( + "The chained asynchronous operation returned" + + " 'null' where the promise instance is expected" + ) + ); + } + } + + public IPromise Chain(Func<IPromise> chained, Func<Exception, IPromise> error) { + return Chain(chained, error, null); + } + + public IPromise Chain(Func<IPromise> chained) { + return Chain(chained, null, null); + } + + public IPromise On(Action success, Action<Exception> error, Action cancel) { + AddHandler(new EventDescriptor(success,error,cancel, StubDeferred.instance)); + return this; + } + + public IPromise On(Action success, Action<Exception> error) { + AddHandler(new EventDescriptor(success, error, null, StubDeferred.instance)); + return this; + } + + public IPromise On(Action success) { + Listen(PromiseEventType.Success, success); + return this; + } + + IPromise IPromise.On(Action handler, PromiseEventType events) { + Listen(events,handler); + return this; + } + + public IPromise Error(Action<Exception> error) { + var promise = new Promise(); + if (error != null) + On( + (Action<T>)null, + ex => { + try { + error(ex); + promise.Resolve(); + } catch (Exception ex2) { + promise.Reject(ex2); + } + }); + else + Listen(PromiseEventType.Error, promise.Resolve); + return promise; + } + + public IPromise Cancelled(Action handler) { + var promise = new Promise(); + if (handler != null) + On( + (Action<T>)null, + null, + () => { + try { + handler(); + promise.Resolve(); + } catch (Exception ex) { + promise.Reject(ex); + } + }); + else + Listen(PromiseEventType.Cancelled, promise.Resolve); + return promise; + } + + public IPromise<T2> Cast<T2>() { + return (IPromise<T2>)this; + } + + #region implemented abstract members of AbstractPromise + + protected override void SignalSuccess(IDeferred<T> handler) { + handler.Resolve(m_result); + } + + protected override void SignalError(IDeferred<T> handler, Exception error) { + handler.Reject(error); + } + + protected override void SignalCancelled(IDeferred<T> handler) { + handler.Cancel(); + } + + protected override void Listen(PromiseEventType events, Action handler) { + if (handler != null) + AddHandler(new ListenerDescriptor(handler, events)); + } + + #endregion + + public static IPromise<T> ResultToPromise(T value) { + var p = new Promise<T>(); + p.Resolve(value); + return p; + } + + public static IPromise<T> ExceptionToPromise(Exception error) { + var p = new Promise<T>(); + p.Reject(error); + return p; + } + + } +}