|
|
using System;
|
|
|
using Implab.Parallels;
|
|
|
using System.Threading;
|
|
|
using System.Reflection;
|
|
|
using System.Diagnostics;
|
|
|
|
|
|
namespace Implab {
|
|
|
/// <summary>
|
|
|
/// Abstract class for creation of custom one-shot thread safe events.
|
|
|
/// </summary>
|
|
|
/// <remarks>
|
|
|
/// <para>
|
|
|
/// An event is something that should happen in the future and the
|
|
|
/// triggering of the event causes execution of some pending actions
|
|
|
/// which are formely event handlers. One-shot events occur only once
|
|
|
/// and any handler added after the event is triggered should run
|
|
|
/// without a delay.
|
|
|
/// </para>
|
|
|
/// <para>
|
|
|
/// The lifecycle of the one-shot event is tipically consists of following
|
|
|
/// phases.
|
|
|
/// <list>
|
|
|
/// <description>Pending state. This is the initial state of the event. Any
|
|
|
/// handler added to the event will be queued for the future execution.
|
|
|
/// </description>
|
|
|
/// <description>Transitional state. This is intermediate state between pending
|
|
|
/// and fulfilled states, during this state internal initialization and storing
|
|
|
/// of the result occurs.
|
|
|
/// </description>
|
|
|
/// <description>Fulfilled state. The event contains the result, all queued
|
|
|
/// handlers are signalled to run and newly added handlers are executed
|
|
|
/// immediatelly.
|
|
|
/// </description>
|
|
|
/// </list>
|
|
|
/// </para>
|
|
|
/// </remarks>
|
|
|
public abstract class AbstractEvent<THandler> where THandler : class {
|
|
|
const int PENDING_SATE = 0;
|
|
|
|
|
|
const int TRANSITIONAL_STATE = 1;
|
|
|
|
|
|
const int FULFILLED_STATE = 2;
|
|
|
|
|
|
volatile int m_state;
|
|
|
|
|
|
THandler m_handler;
|
|
|
SimpleAsyncQueue<THandler> m_extraHandlers;
|
|
|
|
|
|
public bool IsFulfilled {
|
|
|
get {
|
|
|
return m_state > TRANSITIONAL_STATE;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#region state managment
|
|
|
protected bool BeginTransit() {
|
|
|
return PENDING_SATE == Interlocked.CompareExchange(ref m_state, TRANSITIONAL_STATE, PENDING_SATE);
|
|
|
}
|
|
|
|
|
|
protected void CompleteTransit() {
|
|
|
#if DEBUG
|
|
|
if (TRANSITIONAL_STATE != Interlocked.CompareExchange(ref m_state, FULFILLED_STATE, TRANSITIONAL_STATE))
|
|
|
throw new InvalidOperationException("Can't complete transition when the object isn't in the transitional state");
|
|
|
#else
|
|
|
m_state = state;
|
|
|
#endif
|
|
|
Signal();
|
|
|
}
|
|
|
|
|
|
protected void WaitTransition() {
|
|
|
if (m_state == TRANSITIONAL_STATE) {
|
|
|
SpinWait spin;
|
|
|
do {
|
|
|
spin.SpinOnce();
|
|
|
} while (m_state == TRANSITIONAL_STATE);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
protected abstract void SignalHandler(THandler handler);
|
|
|
|
|
|
void Signal() {
|
|
|
THandler handler;
|
|
|
while (TryDequeueHandler(out handler))
|
|
|
SignalHandler(handler);
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
protected abstract Signal GetFulfillSignal();
|
|
|
|
|
|
#region synchronization traits
|
|
|
protected void WaitResult(int timeout) {
|
|
|
if (!(IsFulfilled || GetFulfillSignal().Wait(timeout)))
|
|
|
throw new TimeoutException();
|
|
|
}
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region handlers managment
|
|
|
|
|
|
protected void AddHandler(THandler handler) {
|
|
|
|
|
|
if (IsFulfilled) {
|
|
|
// the promise is in the resolved state, just invoke the handler
|
|
|
SignalHandler(handler);
|
|
|
} else {
|
|
|
EnqueueHandler(handler);
|
|
|
|
|
|
if (IsFulfilled && TryDequeueHandler(out handler))
|
|
|
// if the promise have been resolved while we was adding the handler to the queue
|
|
|
// we can't guarantee that someone is still processing it
|
|
|
// therefore we need to fetch a handler from the queue and execute it
|
|
|
// note that fetched handler may be not the one that we have added
|
|
|
// even we can fetch no handlers at all :)
|
|
|
SignalHandler(handler);
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
void EnqueueHandler(THandler handler) {
|
|
|
if (Interlocked.CompareExchange(ref m_handler, handler, null) != null) {
|
|
|
if (m_extraHandlers == null)
|
|
|
// compare-exchange will protect from loosing already created queue
|
|
|
Interlocked.CompareExchange(ref m_extraHandlers, new SimpleAsyncQueue<THandler>(), null);
|
|
|
m_extraHandlers.Enqueue(handler);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
bool TryDequeueHandler(out THandler handler) {
|
|
|
handler = Interlocked.Exchange(ref m_handler, null);
|
|
|
if (handler != null)
|
|
|
return true;
|
|
|
return m_extraHandlers != null && m_extraHandlers.TryDequeue(out handler);
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|