# HG changeset patch # User cin # Date 2014-09-10 13:53:05 # Node ID c761fc982e1dc3bd8d637e42cbcd7a999a166687 # Parent 4439140706d023a49f04e826ea28cfd093a6949b Refactoring of the IPromise interface Added tests diff --git a/Implab.Fx/PromiseHelpers.cs b/Implab.Fx/PromiseHelpers.cs --- a/Implab.Fx/PromiseHelpers.cs +++ b/Implab.Fx/PromiseHelpers.cs @@ -17,71 +17,25 @@ namespace Implab.Fx /// /// client /// .Get("description.txt") // returns a promise - /// .DirectToControl(m_ctl) // handle the promise in the thread of the control + /// .DispatchToControl(m_ctl) // handle the promise in the thread of the control /// .Then( /// description => m_ctl.Text = description // now it's safe /// ) /// - public static Promise DispatchToControl(this Promise that, Control ctl) + public static IPromise DispatchToControl(this IPromise that, Control ctl) { - if (that == null) - throw new ArgumentNullException("that"); - if (ctl == null) - throw new ArgumentNullException("ctl"); + Safe.ArgumentNotNull(that, "that"); + Safe.ArgumentNotNull(ctl, "ctl"); var directed = new ControlBoundPromise(ctl,that,true); - that.Then( + that.Last( directed.Resolve, - err => - { - directed.Reject(err); - return default(T); - } + directed.Reject, + directed.Cancel ); return directed; } - - /// - /// Направляет обработку обещания РІ текущий поток, если Сѓ него существует контекст синхронизации. - /// - /// РўРёРї результата обещания. - /// Обещание которое нужно обработать РІ текущем потоке. - /// Перенаправленное обещание. - public static Promise DispatchToCurrentThread(this Promise that) - { - var sync = SynchronizationContext.Current; - if (sync == null) - throw new InvalidOperationException("The current thread doesn't have a syncronization context"); - return DispatchToSyncContext(that, sync); - } - - /// - /// Направляет обработку обещания РІ указанный контекст синхронизации. - /// - /// РўРёРї результата обещания. - /// Обещание, которое требуется обработать РІ указанном контексте синхронизации. - /// Контекст синхронизации РІ который будет направлено обещание. - /// РќРѕРІРѕРµ обещание, которое будет обрабатываться РІ указанном контексте. - public static Promise DispatchToSyncContext(this Promise that, SynchronizationContext sync) - { - if (that == null) - throw new ArgumentNullException("that"); - if (sync == null) - throw new ArgumentNullException("sync"); - - var d = new Promise(); - - that.Then( - res => sync.Post(state => d.Resolve(res), null), - err => { - sync.Post(state => d.Reject(err), null); - return default(T); - } - ); - - return d; - } } } diff --git a/Implab.Fx/Properties/AssemblyInfo.cs b/Implab.Fx/Properties/AssemblyInfo.cs --- a/Implab.Fx/Properties/AssemblyInfo.cs +++ b/Implab.Fx/Properties/AssemblyInfo.cs @@ -32,5 +32,4 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("2.0.*")] diff --git a/Implab.Test/AsyncTests.cs b/Implab.Test/AsyncTests.cs --- a/Implab.Test/AsyncTests.cs +++ b/Implab.Test/AsyncTests.cs @@ -38,6 +38,38 @@ namespace Implab.Test { } [TestMethod] + public void CancelExceptionTest() { + var p = new Promise(); + p.Cancel(); + + var p2 = p.Cancelled(() => { + throw new ApplicationException("CANCELLED"); + }); + + try { + p2.Join(); + Assert.Fail(); + } catch (ApplicationException err) { + Assert.AreEqual("CANCELLED", err.InnerException.Message); + } + + } + + [TestMethod] + public void ContinueOnCancelTest() { + var p = new Promise(); + p.Cancel(); + + var p2 = p + .Cancelled(() => { + throw new ApplicationException("CANCELLED"); + }) + .Error(e => true); + + Assert.AreEqual(true, p2.Join()); + } + + [TestMethod] public void JoinSuccessTest() { var p = new Promise(); p.Resolve(100); @@ -63,7 +95,7 @@ namespace Implab.Test { public void MapTest() { var p = new Promise(); - var p2 = p.Map(x => x.ToString()); + var p2 = p.Then(x => x.ToString()); p.Resolve(100); Assert.AreEqual(p2.Join(), "100"); @@ -185,8 +217,8 @@ namespace Implab.Test { var stop = new ManualResetEvent(false); int total = 0; - int itemsPerWriter = 1000; - int writersCount = 3; + int itemsPerWriter = 10000; + int writersCount = 10; for (int i = 0; i < writersCount; i++) { Interlocked.Increment(ref writers); @@ -318,7 +350,7 @@ namespace Implab.Test { .Chain(x => PromiseHelper .Sleep(200, "Hi, " + x) - .Map(y => y) + .Then(y => y) .Cancelled(() => flags[1] = true) ) .Cancelled(() => flags[2] = true); @@ -341,7 +373,7 @@ namespace Implab.Test { // завершаться ошибкой OperationCanceledException var p = PromiseHelper .Sleep(1, "Hi, HAL!") - .Chain(x => { + .Then(x => { // запускаем две асинхронные операции var result = PromiseHelper.Sleep(1000, "HEM ENABLED!!!"); // вторая операция отменяет первую до завершения @@ -360,16 +392,15 @@ namespace Implab.Test { [TestMethod] public void ChainedCancel2Test() { // при отмене цепочки обещаний, вложенные операции также должны отменяться - IPromise p = null; var pSurvive = new Promise(); var hemStarted = new ManualResetEvent(false); - p = PromiseHelper + var p = PromiseHelper .Sleep(1, "Hi, HAL!") .Chain(x => { hemStarted.Set(); // запускаем две асинхронные операции var result = PromiseHelper - .Sleep(1000, "HEM ENABLED!!!") + .Sleep(10000, "HEM ENABLED!!!") .Then(s => pSurvive.Resolve(false)); result diff --git a/Implab/Diagnostics/TraceContext.cs b/Implab/Diagnostics/TraceContext.cs --- a/Implab/Diagnostics/TraceContext.cs +++ b/Implab/Diagnostics/TraceContext.cs @@ -215,7 +215,7 @@ namespace Implab.Diagnostics { Safe.ArgumentNotNull(promise, "promise"); var ctx = DetachLogicalOperation(); - promise.Finally(() => { + promise.Anyway(() => { var old = _current; TraceContext.Attach(ctx); TraceContext.Current.EndLogicalOperation(); diff --git a/Implab/ICancellable.cs b/Implab/ICancellable.cs --- a/Implab/ICancellable.cs +++ b/Implab/ICancellable.cs @@ -5,6 +5,6 @@ using System.Text; namespace Implab { public interface ICancellable { - bool Cancel(); + void Cancel(); } } diff --git a/Implab/IPromise.cs b/Implab/IPromise.cs --- a/Implab/IPromise.cs +++ b/Implab/IPromise.cs @@ -52,7 +52,7 @@ namespace Implab { /// /// Обработчик. /// После обработке ошибки, РѕРЅР° передается дальше. - IPromise Finally(Action handler); + IPromise Anyway(Action handler); /// /// Обработчик для регистрации отмены обещания, событие отмены РЅРµ может быть подавлено. /// diff --git a/Implab/IPromiseT.cs b/Implab/IPromiseT.cs --- a/Implab/IPromiseT.cs +++ b/Implab/IPromiseT.cs @@ -4,34 +4,40 @@ using System.Linq; using System.Text; namespace Implab { - public interface IPromise: IPromise { + public interface IPromise : IPromise { new T Join(); new T Join(int timeout); + void Last(ResultHandler success, ErrorHandler error, Action cancel); + + void Last(ResultHandler success, ErrorHandler error); + + void Last(ResultHandler success); + IPromise Then(ResultHandler success, ErrorHandler error, Action cancel); IPromise Then(ResultHandler success, ErrorHandler error); IPromise Then(ResultHandler success); - void Last(ResultHandler success, ErrorHandler error, Action cancel); - void Last(ResultHandler success, ErrorHandler error); - void Last(ResultHandler success); + IPromise Then(ResultMapper mapper, ErrorHandler error, Action cancel); + + IPromise Then(ResultMapper mapper, ErrorHandler error); + + IPromise Then(ResultMapper mapper); + + IPromise Chain(ResultMapper> chained, ErrorHandler> error, Action cancel); + + IPromise Chain(ResultMapper> chained, ErrorHandler> error); + + IPromise Chain(ResultMapper> chained); IPromise Error(ErrorHandler error); - IPromise Then(ResultMapper mapper, ErrorHandler error); - - IPromise Then(ResultMapper mapper); - - IPromise Then(ChainedOperation chained, ErrorHandler error); - - IPromise Then(ChainedOperation chained); - new IPromise Cancelled(Action handler); - new IPromise Finally(Action handler); + new IPromise Anyway(Action handler); } } diff --git a/Implab/Parallels/ArrayTraits.cs b/Implab/Parallels/ArrayTraits.cs --- a/Implab/Parallels/ArrayTraits.cs +++ b/Implab/Parallels/ArrayTraits.cs @@ -29,7 +29,7 @@ namespace Implab.Parallels { m_pending = source.Length; m_action = action; - m_promise.Finally(Dispose); + m_promise.Anyway(Dispose); InitPool(); } @@ -85,7 +85,7 @@ namespace Implab.Parallels { m_transform = transform; m_traceContext = TraceContext.Snapshot(); - m_promise.Finally(Dispose); + m_promise.Anyway(Dispose); InitPool(); } @@ -138,7 +138,7 @@ namespace Implab.Parallels { return iter.Promise; } - public static IPromise ChainedMap(this TSrc[] source, ChainedOperation transform, int threads) { + public static IPromise ChainedMap(this TSrc[] source, ResultMapper> transform, int threads) { if (source == null) throw new ArgumentNullException("source"); if (transform == null) @@ -165,7 +165,7 @@ namespace Implab.Parallels { semaphore.WaitOne(); try { var p1 = transform(source[i]); - p1.Finally(() => semaphore.Release()); + p1.Anyway(() => semaphore.Release()); p1.Then( x => { res[idx] = x; @@ -186,7 +186,7 @@ namespace Implab.Parallels { return 0; }); - return promise.Finally(semaphore.Dispose); + return promise.Anyway(semaphore.Dispose); } } diff --git a/Implab/Promise.cs b/Implab/Promise.cs --- a/Implab/Promise.cs +++ b/Implab/Promise.cs @@ -11,7 +11,6 @@ namespace Implab { public delegate T ErrorHandler(Exception e); public delegate void ResultHandler(T result); public delegate TNew ResultMapper(TSrc result); - public delegate IPromise ChainedOperation(TSrc result); /// /// Класс для асинхронного получения результатов. Так называемое "обещание". @@ -121,10 +120,15 @@ namespace Implab { public Promise(IPromise parent, bool cancellable) { m_cancellable = cancellable; if (parent != null) - Cancelled(() => { - if (parent.IsExclusive) - parent.Cancel(); - }); + AddHandler( + null, + null, + () => { + if (parent.IsExclusive) + parent.Cancel(); + }, + null + ); } bool BeginTransit() { @@ -210,22 +214,14 @@ namespace Implab { /// /// Отменяет операцию, если это возможно. /// - /// true Операция была отменена, обработчики РЅРµ Р±СѓРґСѓС‚ вызваны.false отмена РЅРµ возможна, поскольку обещание уже выполнено Рё обработчики отработали. - public bool Cancel() { + /// Для определения была ли операция отменена следует использовать свойство . + public void Cancel() { if (m_cancellable && BeginTransit()) { CompleteTransit(CANCELLED_STATE); OnStateChanged(); - return true; } - return false; } - // сделано для возвращаемого типа void - protected void InternalCancel() { - Cancel(); - } - - public IPromise Then(ResultHandler success, ErrorHandler error, Action cancel) { if (success == null && error == null && cancel == null) return this; @@ -255,30 +251,7 @@ namespace Implab { return medium; } - public IPromise Then(Action success, ErrorHandler error, Action cancel) { - return Then( - x => success(), - e => { - error(e); - return default(T); - }, - cancel - ); - } - - public IPromise Then(Action success, ErrorHandler error) { - return Then( - x => success(), - e => { - error(e); - return default(T); - } - ); - } - - public IPromise Then(Action success) { - return Then(x => success()); - } + public IPromise Then(ResultHandler success) { @@ -292,6 +265,23 @@ namespace Implab { return medium; } + /// + /// Последний обработчик РІ цепочки обещаний. + /// + /// + /// + /// + /// + /// + /// Данный метод РЅРµ создает связанного СЃ текущим обещания Рё предназначен для окончания + /// фсинхронной цепочки. + /// + /// + /// Если данный метод вызвать несколько раз, либо добавить РґСЂСѓРіРёРµ обработчики, то цепочка + /// РЅРµ будет одиночной Рё, как следствие, будет невозможна отмена + /// всей цепи обещаний СЃРЅРёР·Сѓ (СЃ самого последнего обещания). + /// + /// public void Last(ResultHandler success, ErrorHandler error, Action cancel) { if (success == null && error == null && cancel == null) return; @@ -313,18 +303,6 @@ namespace Implab { Last(success, null, null); } - public void Last(Action success,ErrorHandler error, Action cancel) { - Last(x => success(), error, cancel); - } - - public void Last(Action success,ErrorHandler error) { - Last(x => success(), error, null); - } - - public void Last(Action success) { - Last(x => success(), null, null); - } - public IPromise Error(ErrorHandler error) { if (error == null) return this; @@ -371,44 +349,56 @@ namespace Implab { /// Обработчик ошибки. Данный обработчик получит /// исключение возникшее РїСЂРё выполнении операции. /// РќРѕРІРѕРµ обещание, которое будет выполнено РїСЂРё выполнении РёСЃС…РѕРґРЅРѕРіРѕ обещания. - public IPromise Then(ResultMapper mapper, ErrorHandler error) { - if (mapper == null) - throw new ArgumentNullException("mapper"); + public IPromise Then(ResultMapper mapper, ErrorHandler error, Action cancel) { + Safe.ArgumentNotNull(mapper, "mapper"); + + // создаем прицепленное обещание + var medium = new Promise(this, true); - // создаем прицепленное обещание - var chained = new Promise(this, true); - - ResultHandler resultHandler = result => chained.Resolve(mapper(result)); + ResultHandler resultHandler = result => medium.Resolve(mapper(result)); ErrorHandler errorHandler; if (error != null) errorHandler = e => { try { - return error(e); + medium.Resolve(error(e)); } catch (Exception e2) { // РІ случае ошибки нужно передать исключение дальше РїРѕ цепочке - chained.Reject(e2); + medium.Reject(e2); } return default(T); }; else errorHandler = e => { - chained.Reject(e); + medium.Reject(e); return default(T); }; + Action cancelHandler; + if (cancel != null) + cancelHandler = () => { + cancel(); + medium.Cancel(); + }; + else + cancelHandler = medium.Cancel; + AddHandler( resultHandler, errorHandler, - chained.InternalCancel, + cancelHandler, null ); - return chained; + return medium; + } + + public IPromise Then(ResultMapper mapper, ErrorHandler error) { + return Then(mapper, error, null); } public IPromise Then(ResultMapper mapper) { - return Then(mapper, null); + return Then(mapper, null, null); } /// @@ -421,7 +411,9 @@ namespace Implab { /// Обработчик ошибки. Данный обработчик получит /// исключение возникшее РїСЂРё выполнении текуещй операции. /// РќРѕРІРѕРµ обещание, которое будет выполнено РїРѕ окончанию указанной аснхронной операции. - public IPromise Then(ChainedOperation chained, ErrorHandler error) { + public IPromise Chain(ResultMapper> chained, ErrorHandler> error, Action cancel) { + + Safe.ArgumentNotNull(chained, "chained"); // проблема РІ том, что РЅР° момент связывания еще РЅРµ начата асинхронная операция, поэтому нужно // создать посредника, Рє которому Р±СѓРґСѓС‚ подвызяваться следующие обработчики. @@ -435,12 +427,10 @@ namespace Implab { var promise = chained(result); - promise.Then( + promise.Last( medium.Resolve, - err => { - medium.Reject(err); - throw new TransientPromiseException(err); - } + medium.Reject, + () => medium.Reject(new OperationCanceledException()) // внешняя отмена связанной операции рассматривается как ошибка ); // notify chained operation that it's not needed anymore @@ -450,41 +440,70 @@ namespace Implab { if (promise.IsExclusive) promise.Cancel(); }); - - // внешняя отмена связанной операции рассматривается как ошибка - promise.Cancelled(() => medium.Reject(new OperationCanceledException())); }; - ErrorHandler errorHandler = delegate(Exception e) { - if (error != null) { + ErrorHandler errorHandler; + + if (error != null) + errorHandler = delegate(Exception e) { try { - return error(e); + var promise = error(e); + + promise.Last( + medium.Resolve, + medium.Reject, + () => medium.Reject(new OperationCanceledException()) // внешняя отмена связанной операции рассматривается как ошибка + ); + + // notify chained operation that it's not needed anymore + // РїРѕСЂСЏРґРѕРє вызова Then, Cancelled важен, поскольку РѕС‚ этого + // зависит IsExclusive + medium.Cancelled(() => { + if (promise.IsExclusive) + promise.Cancel(); + }); } catch (Exception e2) { medium.Reject(e2); - return default(T); } - } - // РІ случае ошибки нужно передать исключение дальше РїРѕ цепочке - medium.Reject(e); - return default(T); - }; + return default(T); + }; + else + errorHandler = err => { + medium.Reject(err); + return default(T); + }; + + + Action cancelHandler; + if (cancel != null) + cancelHandler = () => { + if (cancel != null) + cancel(); + medium.Cancel(); + }; + else + cancelHandler = medium.Cancel; AddHandler( resultHandler, errorHandler, - medium.InternalCancel, + cancelHandler, null ); return medium; } - public IPromise Then(ChainedOperation chained) { - return Then(chained, null); + public IPromise Chain(ResultMapper> chained, ErrorHandler> error) { + return Chain(chained, error, null); + } + + public IPromise Chain(ResultMapper> chained) { + return Chain(chained, null, null); } public IPromise Cancelled(Action handler) { - var medium = new Promise(this, true); + var medium = new Promise(this,true); AddHandler(null, null, handler, medium); return medium; } @@ -494,9 +513,9 @@ namespace Implab { /// /// The handler that will be called anyway /// self - public IPromise Finally(Action handler) { - if (handler == null) - throw new ArgumentNullException("handler"); + public IPromise Anyway(Action handler) { + Safe.ArgumentNotNull(handler, "handler"); + AddHandler( x => handler(), e => { @@ -541,7 +560,7 @@ namespace Implab { /// Результат выполнения обещания public T Join(int timeout) { var evt = new ManualResetEvent(false); - Finally(() => evt.Set()); + Anyway(() => evt.Set()); if (!evt.WaitOne(timeout, true)) throw new TimeoutException(); @@ -736,12 +755,49 @@ namespace Implab { #region IPromiseBase explicit implementation + IPromise IPromise.Then(Action success, ErrorHandler error, Action cancel) { + return Then( + x => success(), + e => { + error(e); + return default(T); + }, + cancel + ); + } + + IPromise IPromise.Then(Action success, ErrorHandler error) { + return Then( + x => success(), + e => { + error(e); + return default(T); + } + ); + } + + IPromise IPromise.Then(Action success) { + return Then(x => success()); + } + + void IPromise.Last(Action success, ErrorHandler error, Action cancel) { + Last(x => success(), error, cancel); + } + + void IPromise.Last(Action success, ErrorHandler error) { + Last(x => success(), error, null); + } + + void IPromise.Last(Action success) { + Last(x => success(), null, null); + } + IPromise IPromise.Error(ErrorHandler error) { return Error(error); } - IPromise IPromise.Finally(Action handler) { - return Finally(handler); + IPromise IPromise.Anyway(Action handler) { + return Anyway(handler); } IPromise IPromise.Cancelled(Action handler) { diff --git a/Implab/PromiseExtensions.cs b/Implab/PromiseExtensions.cs --- a/Implab/PromiseExtensions.cs +++ b/Implab/PromiseExtensions.cs @@ -14,12 +14,10 @@ namespace Implab { var p = new SyncContextPromise(context, that, true); - that.Then( - x => p.Resolve(x), - e => { - p.Reject(e); - return default(T); - } + that.Last( + p.Resolve, + p.Reject, + p.Cancel ); return p; } @@ -30,12 +28,10 @@ namespace Implab { var p = new SyncContextPromise(context, that, true); - that.Then( - x => p.Resolve(x), - e => { - p.Reject(e); - return default(T); - } + that.Last( + p.Resolve, + p.Reject, + p.Cancel ); return p; } diff --git a/Implab/Properties/AssemblyInfo.cs b/Implab/Properties/AssemblyInfo.cs --- a/Implab/Properties/AssemblyInfo.cs +++ b/Implab/Properties/AssemblyInfo.cs @@ -16,7 +16,7 @@ using System.Runtime.InteropServices; // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("2.0.*")] [assembly: ComVisible(false)] // The following attributes are used to specify the signing key for the assembly, diff --git a/Implab/TaskController.cs b/Implab/TaskController.cs --- a/Implab/TaskController.cs +++ b/Implab/TaskController.cs @@ -92,14 +92,10 @@ namespace Implab } } - public bool Cancel() { + public void Cancel() { lock (m_lock) { - if (!m_cancelled) { + if (!m_cancelled) m_cancelled = true; - return true; - } else { - return false; - } } }