AbstractEvent.cs
130 lines
| 4.4 KiB
| text/x-csharp
|
CSharpLexer
/ Implab / AbstractEvent.cs
|
|
r144 | using System; | ||
| using Implab.Parallels; | ||||
| using System.Threading; | ||||
| using System.Reflection; | ||||
|
|
r242 | using System.Diagnostics; | ||
|
|
r144 | |||
| namespace Implab { | ||||
|
|
r243 | /// <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> | ||||
|
|
r242 | public abstract class AbstractEvent<THandler> where THandler : class { | ||
|
|
r244 | const int PendingState = 0; | ||
|
|
r242 | |||
|
|
r244 | const int TransitionalState = 1; | ||
|
|
r243 | |||
|
|
r244 | const int ResolvedState = 2; | ||
|
|
r144 | |||
|
|
r242 | volatile int m_state; | ||
|
|
r144 | |||
|
|
r242 | THandler m_handler; | ||
|
|
r233 | SimpleAsyncQueue<THandler> m_extraHandlers; | ||
|
|
r144 | |||
|
|
r244 | public bool IsResolved { | ||
|
|
r243 | get { | ||
|
|
r244 | return m_state > TransitionalState; | ||
|
|
r243 | } | ||
| } | ||||
|
|
r144 | #region state managment | ||
|
|
r242 | protected bool BeginTransit() { | ||
|
|
r244 | return PendingState == Interlocked.CompareExchange(ref m_state, TransitionalState, PendingState); | ||
|
|
r144 | } | ||
|
|
r243 | protected void CompleteTransit() { | ||
|
|
r242 | #if DEBUG | ||
|
|
r244 | if (TransitionalState != Interlocked.CompareExchange(ref m_state, ResolvedState, TransitionalState)) | ||
|
|
r144 | throw new InvalidOperationException("Can't complete transition when the object isn't in the transitional state"); | ||
|
|
r242 | #else | ||
| m_state = state; | ||||
| #endif | ||||
| Signal(); | ||||
|
|
r144 | } | ||
|
|
r242 | protected void WaitTransition() { | ||
|
|
r244 | if (m_state == TransitionalState) { | ||
|
|
r248 | SpinWait spin = new SpinWait(); | ||
|
|
r242 | do { | ||
| spin.SpinOnce(); | ||||
|
|
r244 | } while (m_state == TransitionalState); | ||
|
|
r144 | } | ||
| } | ||||
|
|
r243 | protected abstract void SignalHandler(THandler handler); | ||
|
|
r144 | |||
|
|
r156 | void Signal() { | ||
|
|
r242 | THandler handler; | ||
| while (TryDequeueHandler(out handler)) | ||||
|
|
r243 | SignalHandler(handler); | ||
|
|
r144 | } | ||
| #endregion | ||||
| #region handlers managment | ||||
| protected void AddHandler(THandler handler) { | ||||
|
|
r244 | if (IsResolved) { | ||
|
|
r144 | // the promise is in the resolved state, just invoke the handler | ||
|
|
r243 | SignalHandler(handler); | ||
|
|
r144 | } else { | ||
|
|
r243 | EnqueueHandler(handler); | ||
|
|
r144 | |||
|
|
r244 | if (IsResolved && TryDequeueHandler(out handler)) | ||
|
|
r144 | // 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 :) | ||||
|
|
r243 | SignalHandler(handler); | ||
|
|
r144 | } | ||
|
|
r242 | |||
| } | ||||
|
|
r243 | 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); | ||||
| } | ||||
| } | ||||
|
|
r242 | 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); | ||||
|
|
r144 | } | ||
| #endregion | ||||
| } | ||||
| } | ||||
