using System; using Implab.Parallels; using System.Threading; using System.Reflection; using System.Diagnostics; namespace Implab { /// /// Abstract class for creation of custom one-shot thread safe events. /// /// /// /// 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. /// /// /// The lifecycle of the one-shot event is tipically consists of following /// phases. /// /// Pending state. This is the initial state of the event. Any /// handler added to the event will be queued for the future execution. /// /// Transitional state. This is intermediate state between pending /// and fulfilled states, during this state internal initialization and storing /// of the result occurs. /// /// Fulfilled state. The event contains the result, all queued /// handlers are signalled to run and newly added handlers are executed /// immediatelly. /// /// /// /// public abstract class AbstractEvent where THandler : class { const int PendingState = 0; const int TransitionalState = 1; const int ResolvedState = 2; volatile int m_state; THandler m_handler; SimpleAsyncQueue m_extraHandlers; public bool IsResolved { get { return m_state > TransitionalState; } } #region state managment protected bool BeginTransit() { return PendingState == Interlocked.CompareExchange(ref m_state, TransitionalState, PendingState); } protected void CompleteTransit() { #if DEBUG if (TransitionalState != Interlocked.CompareExchange(ref m_state, ResolvedState, TransitionalState)) 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 == TransitionalState) { SpinWait spin; do { spin.SpinOnce(); } while (m_state == TransitionalState); } } 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 (!(IsResolved || GetFulfillSignal().Wait(timeout))) throw new TimeoutException(); } #endregion #region handlers managment protected void AddHandler(THandler handler) { if (IsResolved) { // the promise is in the resolved state, just invoke the handler SignalHandler(handler); } else { EnqueueHandler(handler); if (IsResolved && 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(), 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 } }