# HG changeset patch # User cin # Date 1397083169 -14400 # Node ID b255e4aeef1757bf4f600c61579fef621f0887c5 # Parent 8eca2652d2ff9617fb00fa7b9c78f4038ade95cc removed the reference to the parent from the promise object this allows resolved promises to release parents and results they are holding. Added complete set of operations to IPromiseBase interface Subscribing to the cancellation event of the promise should not affect it's IsExclusive property More tests. diff -r 8eca2652d2ff -r b255e4aeef17 Implab.Test/AsyncTests.cs --- a/Implab.Test/AsyncTests.cs Tue Apr 08 23:25:01 2014 +0400 +++ b/Implab.Test/AsyncTests.cs Thu Apr 10 02:39:29 2014 +0400 @@ -328,6 +328,59 @@ Assert.IsTrue(flags[1]); Assert.IsTrue(flags[2]); } + + [TestMethod] + public void ChainedCancel1Test() { + // + // OperationCanceledException + var p = PromiseHelper + .Sleep(1, "Hi, HAL!") + .Chain(x => { + // + var result = PromiseHelper.Sleep(1000, "HEM ENABLED!!!"); + // + PromiseHelper + .Sleep(100, "HAL, STOP!") + .Then(() => result.Cancel()); + return result; + }); + try { + p.Join(); + } catch (TargetInvocationException err) { + Assert.IsTrue(err.InnerException is OperationCanceledException); + } + } + + [TestMethod] + public void ChainedCancel2Test() { + // , + IPromiseBase p = null; + var pSurvive = new Promise(); + var hemStarted = new ManualResetEvent(false); + p = PromiseHelper + .Sleep(1, "Hi, HAL!") + .Chain(x => { + hemStarted.Set(); + // + var result = PromiseHelper + .Sleep(1000, "HEM ENABLED!!!") + .Then(s => pSurvive.Resolve(false)); + + result + .Cancelled(() => pSurvive.Resolve(true)); + + return result; + }); + + hemStarted.WaitOne(); + p.Cancel(); + + try { + p.Join(); + } catch (OperationCanceledException) { + Assert.IsTrue(pSurvive.Join()); + } + } } } diff -r 8eca2652d2ff -r b255e4aeef17 Implab.suo Binary file Implab.suo has changed diff -r 8eca2652d2ff -r b255e4aeef17 Implab/IPromise.cs --- a/Implab/IPromise.cs Tue Apr 08 23:25:01 2014 +0400 +++ b/Implab/IPromise.cs Thu Apr 10 02:39:29 2014 +0400 @@ -8,14 +8,13 @@ public interface IPromise: IPromiseBase { - T Join(); - - T Join(int timeout); + new T Join(); + new T Join(int timeout); IPromise Then(ResultHandler success, ErrorHandler error); IPromise Then(ResultHandler success, ErrorHandler error); IPromise Then(ResultHandler success); - IPromise Error(ErrorHandler error); + new IPromise Error(ErrorHandler error); IPromise Error(ErrorHandler error); IPromise Map(ResultMapper mapper, ErrorHandler error); @@ -24,9 +23,9 @@ IPromise Chain(ChainedOperation chained, ErrorHandler error); IPromise Chain(ChainedOperation chained); - IPromise Cancelled(Action handler); - IPromise Finally(Action handler); - IPromise Anyway(Action handler); + new IPromise Cancelled(Action handler); + new IPromise Finally(Action handler); + new IPromise Anyway(Action handler); } } diff -r 8eca2652d2ff -r b255e4aeef17 Implab/IPromiseBase.cs --- a/Implab/IPromiseBase.cs Tue Apr 08 23:25:01 2014 +0400 +++ b/Implab/IPromiseBase.cs Thu Apr 10 02:39:29 2014 +0400 @@ -23,8 +23,15 @@ IPromiseBase Then(Action success,ErrorHandler error); IPromiseBase Then(Action success); + IPromiseBase Error(ErrorHandler error); + IPromiseBase Anyway(Action handler); + IPromiseBase Finally(Action handler); + IPromiseBase Cancelled(Action handler); IPromise Cast(); + void Join(); + void Join(int timeout); + } } diff -r 8eca2652d2ff -r b255e4aeef17 Implab/Promise.cs --- a/Implab/Promise.cs Tue Apr 08 23:25:01 2014 +0400 +++ b/Implab/Promise.cs Thu Apr 10 02:39:29 2014 +0400 @@ -11,7 +11,7 @@ public delegate T ErrorHandler(Exception e); public delegate void ResultHandler(T result); public delegate TNew ResultMapper(TSrc result); - public delegate IPromise ChainedOperation(TSrc result); + public delegate IPromise ChainedOperation(TSrc result); /// /// Класс для асинхронного получения результатов. Так называемое "обещание". @@ -86,7 +86,6 @@ const int RejectedState = 3; const int CancelledState = 4; - readonly IPromiseBase m_parent; readonly bool m_cancellable; int m_childrenCount = 0; @@ -102,12 +101,15 @@ public Promise(IPromiseBase parent, bool cancellable) { m_cancellable = cancellable; - m_parent = parent; - } - - void InternalCancel() { - // don't try to cancel parent :) - Cancel(false); + if (parent != null) + AddHandler( + null, + null, + () => { + if (parent.IsExclusive) + parent.Cancel(); + } + ); } bool BeginTransit() { @@ -159,6 +161,16 @@ } /// + /// Выполняет обещание, сообщая об успешном выполнении. Результатом выполнения будет пустое значения. + /// + /// + /// Данный вариант удобен в случаях, когда интересен факт выполнения операции, нежели полученное значение. + /// + public void Resolve() { + Resolve(default(T)); + } + + /// /// Выполняет обещание, сообщая об ошибке /// /// @@ -185,7 +197,18 @@ /// /// true Операция была отменена, обработчики не будут вызваны.false отмена не возможна, поскольку обещание уже выполнено и обработчики отработали. public bool Cancel() { - return Cancel(true); + if (BeginTransit()) { + CompleteTransit(CancelledState); + OnStateChanged(); + return true; + } else { + return false; + } + } + + // сделано для возвращаемого типа void + protected void InternalCancel() { + Cancel(); } /// @@ -229,13 +252,11 @@ return medium; } - public IPromiseBase Then(Action success,ErrorHandler error) - { + public IPromiseBase Then(Action success, ErrorHandler error) { return Then(x => success(), error); } - public IPromiseBase Then(Action success) - { + public IPromiseBase Then(Action success) { return Then(x => success()); } @@ -267,7 +288,7 @@ errorHandler = x => { try { medium.Resolve(error(x)); - } catch(Exception e) { + } catch (Exception e) { medium.Reject(e); } }; @@ -338,7 +359,7 @@ if (handler == null) return this; - var medium = new Promise(); + var medium = new Promise(this,true); AddHandler( x => { @@ -377,7 +398,7 @@ throw new ArgumentNullException("mapper"); // создаем прицепленное обещание - var chained = new Promise(); + var chained = new Promise(this,true); ResultHandler resultHandler = result => chained.Resolve(mapper(result)); ErrorHandler errorHandler = delegate(Exception e) { @@ -427,12 +448,21 @@ var promise = chained(result); - // notify chained operation that it's not needed - medium.Cancelled(() => promise.Cancel()); promise.Then( x => medium.Resolve(x), e => medium.Reject(e) ); + + // notify chained operation that it's not needed anymore + // порядок вызова Then, Cancelled важен, поскольку от этого + // зависит IsExclusive + medium.Cancelled(() => { + if(promise.IsExclusive) + promise.Cancel(); + }); + + // внешняя отмена связанной операции рассматривается как ошибка + promise.Cancelled(() => medium.Reject(new OperationCanceledException())); }; ErrorHandler errorHandler = delegate(Exception e) { @@ -531,7 +561,8 @@ } void AddHandler(ResultHandler success, ErrorHandler error, Action cancel) { - Interlocked.Increment(ref m_childrenCount); + if (success != null || error != null) + Interlocked.Increment(ref m_childrenCount); HandlerDescriptor handler = new HandlerDescriptor { resultHandler = success, @@ -588,20 +619,6 @@ } } - protected bool Cancel(bool dependencies) { - if (BeginTransit()) { - CompleteTransit(CancelledState); - OnStateChanged(); - - if (dependencies && m_parent != null && m_parent.IsExclusive) - m_parent.Cancel(); - - return true; - } else { - return false; - } - } - /// /// Объединяет несколько обещаний в одно, результатом которого является массив результатов других обещаний. /// Если хотябы одно из переданных обещаний не будет выполнено, то новое обещение тоже не будет выполнено. @@ -629,20 +646,25 @@ for (int i = 0; i < promises.Count; i++) { var dest = i; - promises[i].Then( - x => { - result[dest] = x; - if(Interlocked.Decrement(ref pending) == 0) - promise.Resolve(result); - }, - e => promise.Reject(e) - ); + if (promises[i] != null) { + promises[i].Then( + x => { + result[dest] = x; + if (Interlocked.Decrement(ref pending) == 0) + promise.Resolve(result); + }, + e => promise.Reject(e) + ); + } else { + if (Interlocked.Decrement(ref pending) == 0) + promise.Resolve(result); + } } promise.Cancelled( () => { - foreach(var d in promises) - if(d.IsExclusive) + foreach (var d in promises) + if (d != null && d.IsExclusive) d.Cancel(); } ); @@ -650,6 +672,47 @@ return promise; } + /// + /// Объединяет несколько обещаний в одно. Результирующее обещание будет выполнено при + /// выполнении всех указанных обещаний. При этом возвращаемые значения первичных обещаний + /// игнорируются. + /// + /// Коллекция первичных обещаний, которые будут объеденены в одно. + /// Новое обещание, объединяющее в себе переданные. + /// + /// Если в коллекции встречаюься null, то они воспринимаются как выполненные обещания. + /// + public static IPromiseBase CreateComposite(ICollection promises) { + if (promises == null) + throw new ArgumentNullException(); + if (promises.Count == 0) + return Promise.ResultToPromise(null); + + int countdown = promises.Count; + + var result = new Promise(); + + foreach (var d in promises) { + if (d == null) { + if (Interlocked.Decrement(ref countdown) == 0) + result.Resolve(null); + } else { + d.Then(() => { + if (Interlocked.Decrement(ref countdown) == 0) + result.Resolve(null); + }); + } + } + + result.Cancelled(() => { + foreach (var d in promises) + if (d != null && d.IsExclusive) + d.Cancel(); + }); + + return result; + } + public static Promise ResultToPromise(T result) { var p = new Promise(); p.Resolve(result); @@ -665,5 +728,35 @@ return p; } + #region IPromiseBase explicit implementation + + IPromiseBase IPromiseBase.Error(ErrorHandler error) { + return Error(error); + } + + IPromiseBase IPromiseBase.Anyway(Action handler) { + return Anyway(handler); + } + + IPromiseBase IPromiseBase.Finally(Action handler) { + return Finally(handler); + } + + IPromiseBase IPromiseBase.Cancelled(Action handler) { + return Cancelled(handler); + } + + void IPromiseBase.Join() { + Join(); + } + + void IPromiseBase.Join(int timeout) { + Join(timeout); + } + + #endregion + + + } }