view Implab/AbstractEvent.cs @ 243:b1e0ffdf3451 v3

working on promises
author cin
date Wed, 24 Jan 2018 19:24:10 +0300
parents cbe10ac0731e
children eee3e49dd1ff
line wrap: on
line source

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