view Implab/Components/PollingComponent.cs @ 209:a867536c68fc v2

Bound promise to CancellationToken Added new states to ExecutionSate enum. Added Safe.Guard() method to handle cleanup of the result of the promise
author cin
date Wed, 16 Nov 2016 03:06:08 +0300
parents 7d07503621fe
children
line wrap: on
line source

using System;
using System.Threading;
using Implab.Diagnostics;

namespace Implab.Components {
    public class PollingComponent : RunnableComponent {
        readonly Timer m_timer;
        readonly Func<Func<ICancellationToken, IPromise>, IPromise> m_dispatcher;
        readonly TimeSpan m_interval;

        readonly object m_lock = new object();

        ActionTask m_pending;

        protected PollingComponent(TimeSpan interval, Func<Func<ICancellationToken, IPromise>, IPromise> dispatcher, bool initialized) : base(initialized) {
            m_timer = new Timer(OnInternalTick);

            m_interval = interval;
            m_dispatcher = dispatcher;
        }

        protected override IPromise OnStart() {
            m_timer.Change(TimeSpan.Zero, m_interval);

            return base.OnStart();
        }

        void OnInternalTick(object state) {
            if (StartTick()) {
                try {
                    if (m_dispatcher != null) {
                        var result = m_dispatcher(OnTick);
                        m_pending.CancellationRequested(result.Cancel);
                        AwaitTick(result);
                    } else {
                        AwaitTick(OnTick(m_pending));
                    }
                } catch (Exception error) {
                    HandleTickError(error);
                }
            }   
        }

        /// <summary>
        /// Checks wheather there is no running handler in the component and marks that the handler is starting.
        /// </summary>
        /// <returns>boolean value, true - the new tick handler may be invoked, false - a tick handler is already running or a component isn't running.</returns>
        /// <remarks>
        /// If the component is stopping no new handlers can be run. Every successful call to this method must be completed with either AwaitTick or HandleTickError handlers.
        /// </remarks>
        protected virtual bool StartTick() {
            lock (m_lock) {
                if (State != ExecutionState.Running || m_pending != null)
                    return false;
                // actually the component may be in a not running state here (stopping, disposed or what ever),
                // but OnStop method also locks on the same object and will handle operation in m_pending
                m_pending = new ActionTask(
                    () => {
                        // only one operation is running, it's safe to assing m_pending from it
                        m_pending = null;
                    },
                    ex => {
                        try {
                            OnTickError(ex);
                            // Analysis disable once EmptyGeneralCatchClause
                        } catch {
                        } finally {
                            m_pending = null;
                        }
                        // suppress error
                    },
                    ex => {
                        try {
                            OnTickCancel(ex);
                            // Analysis disable once EmptyGeneralCatchClause
                        } catch {
                        } finally {
                            m_pending = null;
                        }
                        // supress cancellation
                    },
                    false
                );
                return true;
            }
        }

        /// <summary>
        /// Awaits the tick.
        /// </summary>
        /// <param name="tick">Tick.</param>
        /// <remarks>
        /// This method is called only after StartTick method and m_pending will hold the promise which should be fulfilled.
        /// </remarks>
        void AwaitTick(IPromise tick) {
            if (tick == null) {
                m_pending.Resolve();
            } else {
                tick.On(
                    m_pending.Resolve,
                    m_pending.Reject,
                    m_pending.CancelOperation
                );
            }
        }

        /// <summary>
        /// Handles the tick error.
        /// </summary>
        /// <remarks>
        /// This method is called only after StartTick method and m_pending will hold the promise which should be fulfilled.
        /// </remarks>
        void HandleTickError(Exception error) {
            m_pending.Reject(error);
        }

        protected virtual void OnTickError(Exception error) {
        }

        protected virtual void OnTickCancel(Exception error) {
        }

        /// <summary>
        /// Invoked when the timer ticks, use this method to implement your logic
        /// </summary>
        protected virtual IPromise OnTick(ICancellationToken cancellationToken) {
            return Promise.Success;
        }

        protected override IPromise OnStop() {
            m_timer.Change(-1, -1);

            // the component is in the stopping state
            lock (m_lock) {
                // after this lock no more pending operations could be created
                var pending = m_pending;
                // m_pending could be fulfilled and set to null already
                if (pending != null) {
                    pending.Cancel();
                    return pending.Then(base.OnStop);
                }
            }

            return base.OnStop();
        }

        protected override void Dispose(bool disposing) {
            if (disposing)
                m_timer.Dispose();
            
            base.Dispose(disposing);
        }
    }
}