changeset 33:b255e4aeef17

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.
author cin
date Thu, 10 Apr 2014 02:39:29 +0400
parents 8eca2652d2ff
children dabf79fde388
files Implab.Test/AsyncTests.cs Implab.suo Implab/IPromise.cs Implab/IPromiseBase.cs Implab/Promise.cs
diffstat 5 files changed, 202 insertions(+), 50 deletions(-) [+]
line wrap: on
line diff
--- 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<bool>();
+            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());
+            }
+        }
     }
 }
 
Binary file Implab.suo has changed
--- 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<T>: IPromiseBase
     {
 
-        T Join();
-
-        T Join(int timeout);
+        new T Join();
+        new T Join(int timeout);
 
         IPromise<T> Then(ResultHandler<T> success, ErrorHandler error);
         IPromise<T> Then(ResultHandler<T> success, ErrorHandler<T> error);
         IPromise<T> Then(ResultHandler<T> success);
-        IPromise<T> Error(ErrorHandler error);
+        new IPromise<T> Error(ErrorHandler error);
         IPromise<T> Error(ErrorHandler<T> error);
 
         IPromise<T2> Map<T2>(ResultMapper<T,T2> mapper, ErrorHandler error);
@@ -24,9 +23,9 @@
         IPromise<T2> Chain<T2>(ChainedOperation<T, T2> chained, ErrorHandler error);
         IPromise<T2> Chain<T2>(ChainedOperation<T, T2> chained);
 
-        IPromise<T> Cancelled(Action handler);
-        IPromise<T> Finally(Action handler);
-        IPromise<T> Anyway(Action handler);
+        new IPromise<T> Cancelled(Action handler);
+        new IPromise<T> Finally(Action handler);
+        new IPromise<T> Anyway(Action handler);
 
     }
 }
--- 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<T> Cast<T>();
 
+        void Join();
+        void Join(int timeout);
+
     }
 }
--- 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<out T>(Exception e);
     public delegate void ResultHandler<in T>(T result);
     public delegate TNew ResultMapper<in TSrc, out TNew>(TSrc result);
-    public delegate IPromise<TNew> ChainedOperation<in TSrc,TNew>(TSrc result);
+    public delegate IPromise<TNew> ChainedOperation<in TSrc, TNew>(TSrc result);
 
     /// <summary>
     /// Класс для асинхронного получения результатов. Так называемое "обещание".
@@ -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 @@
         }
 
         /// <summary>
+        /// Выполняет обещание, сообщая об успешном выполнении. Результатом выполнения будет пустое значения.
+        /// </summary>
+        /// <remarks>
+        /// Данный вариант удобен в случаях, когда интересен факт выполнения операции, нежели полученное значение.
+        /// </remarks>
+        public void Resolve() {
+            Resolve(default(T));
+        }
+
+        /// <summary>
         /// Выполняет обещание, сообщая об ошибке
         /// </summary>
         /// <remarks>
@@ -185,7 +197,18 @@
         /// </summary>
         /// <returns><c>true</c> Операция была отменена, обработчики не будут вызваны.<c>false</c> отмена не возможна, поскольку обещание уже выполнено и обработчики отработали.</returns>
         public bool Cancel() {
-            return Cancel(true);
+            if (BeginTransit()) {
+                CompleteTransit(CancelledState);
+                OnStateChanged();
+                return true;
+            } else {
+                return false;
+            }
+        }
+
+        // сделано для возвращаемого типа void
+        protected void InternalCancel() {
+            Cancel();
         }
 
         /// <summary>
@@ -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<T>();
+            var medium = new Promise<T>(this,true);
 
             AddHandler(
                 x => {
@@ -377,7 +398,7 @@
                 throw new ArgumentNullException("mapper");
 
             // создаем прицепленное обещание
-            var chained = new Promise<TNew>();
+            var chained = new Promise<TNew>(this,true);
 
             ResultHandler<T> 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<T> 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;
-            }
-        }
-
         /// <summary>
         /// Объединяет несколько обещаний в одно, результатом которого является массив результатов других обещаний.
         /// Если хотябы одно из переданных обещаний не будет выполнено, то новое обещение тоже не будет выполнено.
@@ -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;
         }
 
+        /// <summary>
+        /// Объединяет несколько обещаний в одно. Результирующее обещание будет выполнено при
+        /// выполнении всех указанных обещаний. При этом возвращаемые значения первичных обещаний
+        /// игнорируются.
+        /// </summary>
+        /// <param name="promises">Коллекция первичных обещаний, которые будут объеденены в одно.</param>
+        /// <returns>Новое обещание, объединяющее в себе переданные.</returns>
+        /// <remarks>
+        /// Если в коллекции встречаюься <c>null</c>, то они воспринимаются как выполненные обещания.
+        /// </remarks>
+        public static IPromiseBase CreateComposite(ICollection<IPromiseBase> promises) {
+            if (promises == null)
+                throw new ArgumentNullException();
+            if (promises.Count == 0)
+                return Promise<object>.ResultToPromise(null);
+
+            int countdown = promises.Count;
+
+            var result = new Promise<object>();
+
+            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<T> ResultToPromise(T result) {
             var p = new Promise<T>();
             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
+
+
+        
     }
 }