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;
- }
}
}