# HG changeset patch # User cin # Date 2013-11-03 14:07:38 # Node ID aa33d0bb8c0cbb2aca37c657e35aedbfdc896b23 # Parent c82e0dfbb4ddf9ae375cf213d9370b9d5835f268 implemeted new cancellable promises concept diff --git a/.hgignore b/.hgignore --- a/.hgignore +++ b/.hgignore @@ -10,3 +10,4 @@ Implab.Fx/obj/ Implab.Fx/bin/ Implab.Fx.Test/bin/ Implab.Fx.Test/obj/ +_ReSharper.Implab/ diff --git a/Implab.Test/AsyncTests.cs b/Implab.Test/AsyncTests.cs --- a/Implab.Test/AsyncTests.cs +++ b/Implab.Test/AsyncTests.cs @@ -1,10 +1,9 @@ using System; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Implab; using System.Reflection; using System.Threading; -namespace Implab.Tests +namespace Implab.Test { [TestClass] public class AsyncTests @@ -90,12 +89,39 @@ namespace Implab.Tests public void PoolTest () { var pid = Thread.CurrentThread.ManagedThreadId; - var p = AsyncPool.Invoke (() => { - return Thread.CurrentThread.ManagedThreadId; - }); + var p = AsyncPool.Invoke (() => Thread.CurrentThread.ManagedThreadId); Assert.AreNotEqual (pid, p.Join ()); } + + [TestMethod] + public void ComplexCase1Test() { + var flags = new bool[3]; + + // op1 (aync 200ms) => op2 (async 200ms) => op3 (sync map) + + var p = PromiseHelper + .Sleep(200, "Alan") + .Cancelled(() => flags[0] = true) + .Chain(x => + PromiseHelper + .Sleep(200, "Hi, " + x) + .Map( y => y ) + .Cancelled(() => flags[1] = true) + ) + .Cancelled(() => flags[2] = true); + Thread.Sleep(300); + p.Cancel(); + try { + Assert.AreEqual(p.Join(), "Hi, Alan"); + Assert.Fail("Shouldn't get here"); + } catch(OperationCanceledException) { + } + + Assert.IsFalse(flags[0]); + Assert.IsTrue(flags[1]); + Assert.IsTrue(flags[2]); + } } } diff --git a/Implab.Test/Implab.Test.csproj b/Implab.Test/Implab.Test.csproj --- a/Implab.Test/Implab.Test.csproj +++ b/Implab.Test/Implab.Test.csproj @@ -46,6 +46,7 @@ + diff --git a/Implab.Test/PromiseHelper.cs b/Implab.Test/PromiseHelper.cs new file mode 100644 --- /dev/null +++ b/Implab.Test/PromiseHelper.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; + +namespace Implab.Test { + class PromiseHelper { + public static Promise Sleep(int timeout, T retVal) { + return AsyncPool.Invoke(() => { + Thread.Sleep(timeout); + return retVal; + }); + } + } +} diff --git a/Implab.suo b/Implab.suo index 0b6524115b93f134aba8070eab347a9c6b46817c..fe568ef8e83a685430ea861c1a089f2705ea5f4b GIT binary patch literal 44544 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 to cancel the whole promise chain, the parent promise will be cancelled only if it has only one promise /// bool Cancel(bool dependencies); + + /// + /// Registers handler for the case when the promise is cencelled. If the promise already cancelled the + /// handler will be invoked immediatelly. + /// + /// The handler + void HandleCancelled(Action handler); } } diff --git a/Implab/Promise.cs b/Implab/Promise.cs --- a/Implab/Promise.cs +++ b/Implab/Promise.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Reflection; -using System.Text; using System.Diagnostics; using System.Threading; @@ -10,9 +8,9 @@ namespace Implab { public delegate void ErrorHandler(Exception e); - public delegate void ResultHandler(T result); - public delegate TNew ResultMapper(TSrc result); - public delegate Promise ChainedOperation(TSrc result); + public delegate void ResultHandler(T result); + public delegate TNew ResultMapper(TSrc result); + public delegate Promise ChainedOperation(TSrc result); /// /// Класс для асинхронного получения результатов. Так называемое "обещание". @@ -55,20 +53,19 @@ namespace Implab { public ErrorHandler errorHandler; } - IPromise m_parent; + readonly IPromise m_parent; LinkedList m_resultHandlers = new LinkedList(); LinkedList m_cancelHandlers = new LinkedList(); - object m_lock = new Object(); - bool m_cancellable; + readonly object m_lock = new Object(); + readonly bool m_cancellable; + int m_childrenCount = 0; PromiseState m_state; T m_result; Exception m_error; - int m_childrenCount; - public Promise() { m_cancellable = true; } @@ -76,15 +73,14 @@ namespace Implab { public Promise(IPromise parent, bool cancellable) { m_cancellable = cancellable; m_parent = parent; + if (parent != null) + parent.HandleCancelled(InternalCancel); } - /// - /// Событие, возникающее при отмене асинхронной операции. - /// - /// - /// Как правило используется для оповещения объекта, выполняющего асинхронную операцию, о том, что ее следует отменить. - /// - public event EventHandler Cancelled; + void InternalCancel() { + // don't try to cancel parent :) + Cancel(false); + } /// /// Выполняет обещание, сообщая об успешном выполнении. @@ -101,14 +97,7 @@ namespace Implab { m_state = PromiseState.Resolved; } - // state has been changed to rejected new handlers can't be added - - foreach (var handler in m_resultHandlers) - InvokeHandler(handler); - - /* ResultHandlerInfo handler; - while (FetchNextHandler(out handler)) - InvokeHandler(handler); */ + OnStateChanged(); } /// @@ -126,14 +115,7 @@ namespace Implab { m_state = PromiseState.Rejected; } - // state has been changed to rejected new handlers can't be added - - foreach (var handler in m_resultHandlers) - InvokeHandler(handler); - - /*ResultHandlerInfo handler; - while (FetchNextHandler(out handler)) - InvokeHandler(handler);*/ + OnStateChanged(); } /// @@ -144,6 +126,39 @@ namespace Implab { return Cancel(true); } + protected virtual void OnStateChanged() { + switch (m_state) { + case PromiseState.Resolved: + foreach (var resultHandlerInfo in m_resultHandlers) + try { + if (resultHandlerInfo.resultHandler != null) + resultHandlerInfo.resultHandler(m_result); + } catch (Exception e) { + try { + if (resultHandlerInfo.errorHandler != null) + resultHandlerInfo.errorHandler(e); + } catch { } + } + break; + case PromiseState.Cancelled: + foreach (var cancelHandler in m_cancelHandlers) + cancelHandler(); + break; + case PromiseState.Rejected: + foreach (var resultHandlerInfo in m_resultHandlers) + try { + if (resultHandlerInfo.errorHandler != null) + resultHandlerInfo.errorHandler(m_error); + } catch { } + break; + default: + throw new InvalidOperationException(String.Format("Promise entered an invalid state {0}", m_state)); + } + + m_resultHandlers = null; + m_cancelHandlers = null; + } + /// /// Добавляет обработчики событий выполнения обещания. /// @@ -162,15 +177,11 @@ namespace Implab { if (success != null) handlerInfo.resultHandler = x => { - try { - success(x); - medium.Resolve(x); - } catch (Exception e) { - medium.Reject(e); - } + success(x); + medium.Resolve(x); }; else - handlerInfo.resultHandler = x => medium.Resolve(x); + handlerInfo.resultHandler = medium.Resolve; if (error != null) handlerInfo.errorHandler = x => { @@ -180,7 +191,7 @@ namespace Implab { medium.Reject(x); }; else - handlerInfo.errorHandler = x => medium.Reject(x); + handlerInfo.errorHandler = medium.Reject; AddHandler(handlerInfo); @@ -203,6 +214,7 @@ namespace Implab { AddHandler(new ResultHandlerInfo { resultHandler = x => { + // to avoid handler being called multiple times we handle exception by ourselfs try { handler(); medium.Resolve(x); @@ -234,20 +246,15 @@ namespace Implab { throw new ArgumentNullException("mapper"); // создаем прицепленное обещание - Promise chained = new Promise(); + var chained = new Promise(); AddHandler(new ResultHandlerInfo() { - resultHandler = delegate(T result) { - try { - // если преобразование выдаст исключение, то сработает reject сцепленного deferred - chained.Resolve(mapper(result)); - } catch (Exception e) { - chained.Reject(e); - } - }, + resultHandler = result => chained.Resolve(mapper(result)), errorHandler = delegate(Exception e) { if (error != null) - error(e); + try { + error(e); + } catch { } // в случае ошибки нужно передать исключение дальше по цепочке chained.Reject(e); } @@ -276,19 +283,21 @@ namespace Implab { // создать посредника, к которому будут подвызяваться следующие обработчики. // когда будет выполнена реальная асинхронная операция, она обратиться к посреднику, чтобы // передать через него результаты работы. - Promise medium = new Promise(); + var medium = new Promise(this, true); - AddHandler(new ResultHandlerInfo() { + AddHandler(new ResultHandlerInfo { resultHandler = delegate(T result) { - try { - chained(result).Then( - x => medium.Resolve(x), - e => medium.Reject(e) - ); - } catch (Exception e) { - // если сцепленное действие выдало исключение вместо обещания, то передаем ошибку по цепочке - medium.Reject(e); - } + if (medium.State == PromiseState.Cancelled) + return; + + var promise = chained(result); + + // notify chained operation that it's not needed + medium.Cancelled(() => promise.Cancel()); + promise.Then( + medium.Resolve, + medium.Reject + ); }, errorHandler = delegate(Exception e) { if (error != null) @@ -305,6 +314,22 @@ namespace Implab { return Chain(chained, null); } + public Promise Cancelled(Action handler) { + if (handler == null) + return this; + lock (m_lock) { + if (m_state == PromiseState.Unresolved) + m_cancelHandlers.AddLast(handler); + else if (m_state == PromiseState.Cancelled) + handler(); + } + return this; + } + + public void HandleCancelled(Action handler) { + Cancelled(handler); + } + /// /// Дожидается отложенного обещания и в случае успеха, возвращает /// его, результат, в противном случае бросает исключение. @@ -327,51 +352,37 @@ namespace Implab { /// Время ожидания /// Результат выполнения обещания public T Join(int timeout) { - ManualResetEvent evt = new ManualResetEvent(false); + var evt = new ManualResetEvent(false); Anyway(() => evt.Set()); + Cancelled(() => evt.Set()); if (!evt.WaitOne(timeout, true)) throw new TimeoutException(); - if (m_error != null) - throw new TargetInvocationException(m_error); - else - return m_result; + switch (State) { + case PromiseState.Resolved: + return m_result; + case PromiseState.Cancelled: + throw new OperationCanceledException(); + case PromiseState.Rejected: + throw new TargetInvocationException(m_error); + default: + throw new ApplicationException(String.Format("Invalid promise state {0}", State)); + } } public T Join() { return Join(Timeout.Infinite); } - /// - /// Данный метод последовательно извлекает обработчики обещания и когда - /// их больше не осталось - ставит состояние "разрешено". - /// - /// Информация об обработчике - /// Признак того, что еще остались обработчики в очереди - bool FetchNextHandler(out ResultHandlerInfo handler) { - handler = default(ResultHandlerInfo); - - lock (this) { - Debug.Assert(m_state != PromiseState.Unresolved); - - if (m_resultHandlers.Count > 0) { - handler = m_resultHandlers.First.Value; - m_resultHandlers.RemoveFirst(); - return true; - } else { - return false; - } - } - } - void AddHandler(ResultHandlerInfo handler) { bool invokeRequired = false; - lock (this) { - if (m_state == PromiseState.Unresolved) + lock (m_lock) { + m_childrenCount++; + if (m_state == PromiseState.Unresolved) { m_resultHandlers.AddLast(handler); - else + } else invokeRequired = true; } @@ -381,18 +392,27 @@ namespace Implab { } void InvokeHandler(ResultHandlerInfo handler) { - if (m_error == null) { - try { - if (handler.resultHandler != null) - handler.resultHandler(m_result); - } catch { } - } - - if (m_error != null) { - try { - if (handler.errorHandler != null) - handler.errorHandler(m_error); - } catch { } + switch (m_state) { + case PromiseState.Resolved: + try { + if (handler.resultHandler != null) + handler.resultHandler(m_result); + } catch (Exception e) { + try { + if (handler.errorHandler != null) + handler.errorHandler(e); + } catch { } + } + break; + case PromiseState.Rejected: + try { + if (handler.errorHandler != null) + handler.errorHandler(m_error); + } catch { } + break; + default: + // do nothing + return; } } @@ -426,15 +446,11 @@ namespace Implab { } } - if (dependencies && m_parent != null && m_parent.IsExclusive) { - // TODO syncronize IsExclusive, AddHandler, Cancel (maybe CancelExclusive) - m_parent.Cancel(true); - } + if (result) + OnStateChanged(); - if (result) { - // state has been changed to cancelled, new handlers can't be added - foreach (var handler in m_cancelHandlers) - handler(); + if (dependencies && m_parent != null && m_parent.IsExclusive) { + m_parent.Cancel(true); } return result; diff --git a/Implab/TaskController.cs b/Implab/TaskController.cs --- a/Implab/TaskController.cs +++ b/Implab/TaskController.cs @@ -14,9 +14,8 @@ namespace Implab /// class TaskController { - object m_lock; + readonly object m_lock; string m_message; - bool m_cancelled; float m_current; float m_max;