Mercurial > pub > ImplabNet
view Implab/Components/PollingComponent.cs @ 244:eee3e49dd1ff v3
working on promises
author | cin |
---|---|
date | Thu, 25 Jan 2018 19:09:16 +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); } } }