##// END OF EJS Templates
Added utility class for safe disposing methods....
Added utility class for safe disposing methods. Added event fireing while promise is cancelled (needs some more work)

File last commit:

r1:6fb3b01ee971 default
r1:6fb3b01ee971 default
Show More
Promise.cs
355 lines | 14.9 KiB | text/x-csharp | CSharpLexer
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Diagnostics;
using System.Threading;
namespace Implab {
public delegate void ErrorHandler(Exception e);
public delegate void ResultHandler<T>(T result);
public delegate TNew ResultMapper<TSrc,TNew>(TSrc result);
public delegate Promise<TNew> ChainedOperation<TSrc,TNew>(TSrc result);
/// <summary>
/// Класс для асинхронного получения результатов. Так называемое "обещание".
/// </summary>
/// <typeparam name="T">Тип получаемого результата</typeparam>
/// <remarks>
/// <para>Сервис при обращении к его методу дает обещаиние о выполнении операции,
/// клиент получив такое обещание может установить ряд обратных вызово для получения
/// событий выполнения обещания, тоесть завершения операции и предоставлении результатов.</para>
/// <para>
/// Обещение может быть как выполнено, так и выполнено с ошибкой. Для подписки на
/// данные события клиент должен использовать методы <c>Then</c>.
/// </para>
/// <para>
/// Сервис, в свою очередь, по окончанию выполнения операции (возможно с ошибкой),
/// использует методы <c>Resolve</c> либо <c>Reject</c> для оповещения клиетна о
/// выполнении обещания.
/// </para>
/// <para>
/// Если сервер успел выполнить обещание еще до того, как клиент на него подписался,
/// то в момент подписки клиента будут вызваны соответсвующие события в синхронном
/// режиме и клиент будет оповещен в любом случае. Иначе, обработчики добавляются в
/// список в порядке подписания и в этом же порядке они будут вызваны при выполнении
/// обещания.
/// </para>
/// <para>
/// Обрабатывая результаты обещания можно преобразовывать результаты либо инициировать
/// связанные асинхронные операции, которые также возвращают обещания. Для этого следует
/// использовать соответствующую форму методе <c>Then</c>.
/// </para>
/// <para>
/// Также хорошим правилом является то, что <c>Resolve</c> и <c>Reject</c> должен вызывать
/// только инициатор обещания иначе могут возникнуть противоречия.
/// </para>
/// </remarks>
public class Promise<T> {
struct ResultHandlerInfo {
public ResultHandler<T> resultHandler;
public ErrorHandler errorHandler;
}
enum State {
Unresolved,
Resolving,
Resolved,
Cancelled
}
LinkedList<ResultHandlerInfo> m_handlersChain = new LinkedList<ResultHandlerInfo>();
State m_state;
bool m_cancellable;
T m_result;
Exception m_error;
public Promise() {
m_cancellable = true;
}
/// <summary>
/// Событие, возникающее при отмене асинхронной операции.
/// </summary>
/// <description>
/// Как правило используется для оповещения объекта, выполняющего асинхронную операцию, о том, что ее следует отменить.
/// </description>
public event EventHandler Cancelled;
/// <summary>
/// Выполняет обещание, сообщая об успешном выполнении.
/// </summary>
/// <param name="result">Результат выполнения.</param>
/// <exception cref="InvalidOperationException">Данное обещание уже выполнено</exception>
public void Resolve(T result) {
lock (this) {
if (m_state == State.Cancelled)
return;
if (m_state != State.Unresolved)
throw new InvalidOperationException("The promise is already resolved");
m_result = result;
m_state = State.Resolving;
}
ResultHandlerInfo handler;
while (FetchNextHandler(out handler))
InvokeHandler(handler);
}
/// <summary>
/// Выполняет обещание, сообщая об ошибке
/// </summary>
/// <param name="error">Исключение возникшее при выполнении операции</param>
/// <exception cref="InvalidOperationException">Данное обещание уже выполнено</exception>
public void Reject(Exception error) {
lock (this) {
if (m_state == State.Cancelled)
return;
if (m_state != State.Unresolved)
throw new InvalidOperationException("The promise is already resolved");
m_error = error;
m_state = State.Resolving;
}
ResultHandlerInfo handler;
while (FetchNextHandler(out handler))
InvokeHandler(handler);
}
/// <summary>
/// Отменяет операцию, если это возможно.
/// </summary>
/// <returns><c>true</c> Операция была отменена, обработчики не будут вызваны.<c>false</c> отмена не возможна, поскольку обещание уже выполнено и обработчики отработали.</returns>
public bool Cancel() {
lock(this) {
if (m_state == State.Unresolved && m_cancellable) {
m_state = State.Cancelled;
EventHandler temp = Cancelled;
if (temp != null)
temp(this,new EventArgs());
return true;
} else
return false;
}
}
/// <summary>
/// Добавляет обработчики событий выполнения обещания.
/// </summary>
/// <param name="success">Обработчик успешного выполнения обещания.
/// Данному обработчику будет передан результат выполнения операции.</param>
/// <param name="error">Обработчик ошибки. Данный обработчик получит
/// исключение возникшее при выполнении операции.</param>
/// <returns>Само обещание</returns>
public Promise<T> Then(ResultHandler<T> success, ErrorHandler error) {
if (success == null && error == null)
return this;
AddHandler(new ResultHandlerInfo() {
resultHandler = success,
errorHandler = error
});
return this;
}
public Promise<T> Then(ResultHandler<T> success) {
return Then (success, null);
}
public Promise<T> Anyway(Action handler) {
if (handler == null)
return this;
AddHandler(new ResultHandlerInfo {
resultHandler = x => handler(),
errorHandler = x => handler()
});
return this;
}
/// <summary>
/// Позволяет преобразовать результат выполения операции к новому типу.
/// </summary>
/// <typeparam name="TNew">Новый тип результата.</typeparam>
/// <param name="mapper">Преобразование результата к новому типу.</param>
/// <param name="error">Обработчик ошибки. Данный обработчик получит
/// исключение возникшее при выполнении операции.</param>
/// <returns>Новое обещание, которое будет выполнено при выполнении исходного обещания.</returns>
public Promise<TNew> Map<TNew>(ResultMapper<T, TNew> mapper, ErrorHandler error) {
if (mapper == null)
throw new ArgumentNullException("mapper");
// создаем прицепленное обещание
Promise<TNew> chained = new Promise<TNew>();
AddHandler(new ResultHandlerInfo() {
resultHandler = delegate(T result) {
try {
// если преобразование выдаст исключение, то сработает reject сцепленного deferred
chained.Resolve(mapper(result));
} catch (Exception e) {
chained.Reject(e);
}
},
errorHandler = delegate(Exception e) {
if (error != null)
error(e);
// в случае ошибки нужно передать исключение дальше по цепочке
chained.Reject(e);
}
});
return chained;
}
public Promise<TNew> Map<TNew>(ResultMapper<T, TNew> mapper) {
return Map (mapper, null);
}
/// <summary>
/// Сцепляет несколько аснхронных операций. Указанная асинхронная операция будет вызвана после
/// выполнения текущей, а результат текущей операции может быть использован для инициализации
/// новой операции.
/// </summary>
/// <typeparam name="TNew">Тип результата указанной асинхронной операции.</typeparam>
/// <param name="chained">Асинхронная операция, которая должна будет начаться после выполнения текущей.</param>
/// <param name="error">Обработчик ошибки. Данный обработчик получит
/// исключение возникшее при выполнении текуещй операции.</param>
/// <returns>Новое обещание, которое будет выполнено по окончанию указанной аснхронной операции.</returns>
public Promise<TNew> Chain<TNew>(ChainedOperation<T, TNew> chained, ErrorHandler error) {
// проблема в том, что на момент связывания еще не начата асинхронная операция, поэтому нужно
// создать посредника, к которому будут подвызяваться следующие обработчики.
// когда будет выполнена реальная асинхронная операция, она обратиться к посреднику, чтобы
// передать через него результаты работы.
Promise<TNew> medium = new Promise<TNew>();
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);
}
},
errorHandler = delegate(Exception e) {
if (error != null)
error(e);
// в случае ошибки нужно передать исключение дальше по цепочке
medium.Reject(e);
}
});
return medium;
}
public Promise<TNew> Chain<TNew>(ChainedOperation<T, TNew> chained) {
return Chain (chained, null);
}
/// <summary>
/// Дожидается отложенного обещания и в случае успеха, возвращает
/// его, результат, в противном случае бросает исключение.
/// </summary>
/// <remarks>
/// <para>
/// Если ожидание обещания было прервано по таймауту, это не значит,
/// что обещание было отменено или что-то в этом роде, это только
/// означает, что мы его не дождались, однако все зарегистрированные
/// обработчики, как были так остались и они будут вызваны, когда
/// обещание будет выполнено.
/// </para>
/// <para>
/// Такое поведение вполне оправдано поскольку таймаут может истечь
/// в тот момент, когда началась обработка цепочки обработчиков, и
/// к тому же текущее обещание может стоять в цепочке обещаний и его
/// отклонение может привести к непрогнозируемому результату.
/// </para>
/// </remarks>
/// <param name="timeout">Время ожидания</param>
/// <returns>Результат выполнения обещания</returns>
public T Join(int timeout) {
ManualResetEvent evt = new ManualResetEvent(false);
Anyway(() => evt.Set());
if (!evt.WaitOne(timeout, true))
throw new TimeoutException();
if (m_error != null)
throw new TargetInvocationException( m_error );
else
return m_result;
}
public T Join() {
return Join(Timeout.Infinite);
}
/// <summary>
/// Данный метод последовательно извлекает обработчики обещания и когда
/// их больше не осталось - ставит состояние "разрешено".
/// </summary>
/// <param name="handler">Информация об обработчике</param>
/// <returns>Признак того, что еще остались обработчики в очереди</returns>
bool FetchNextHandler(out ResultHandlerInfo handler) {
handler = default(ResultHandlerInfo);
lock (this) {
Debug.Assert(m_state == State.Resolving);
if (m_handlersChain.Count > 0) {
handler = m_handlersChain.First.Value;
m_handlersChain.RemoveFirst();
return true;
} else {
m_state = State.Resolved;
return false;
}
}
}
void AddHandler(ResultHandlerInfo handler) {
bool invokeRequired = false;
lock (this) {
if (m_state != State.Resolved)
m_handlersChain.AddLast(handler);
else
invokeRequired = true;
}
// обработчики не должны блокировать сам объект
if (invokeRequired)
InvokeHandler(handler);
}
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 { }
}
}
}
}