using System; using System.Threading; using Implab.Diagnostics; namespace Implab.Components { public class PollingComponent : RunnableComponent { readonly Timer m_timer; readonly Func, IPromise> m_dispatcher; readonly TimeSpan m_interval; readonly object m_lock = new object(); ActionTask m_pending; protected PollingComponent(TimeSpan interval, Func, 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); } } } /// /// Checks wheather there is no running handler in the component and marks that the handler is starting. /// /// boolean value, true - the new tick handler may be invoked, false - a tick handler is already running or a component isn't running. /// /// 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. /// 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; } } /// /// Awaits the tick. /// /// Tick. /// /// This method is called only after StartTick method and m_pending will hold the promise which should be fulfilled. /// void AwaitTick(IPromise tick) { if (tick == null) { m_pending.Resolve(); } else { tick.On( m_pending.Resolve, m_pending.Reject, m_pending.CancelOperation ); } } /// /// Handles the tick error. /// /// /// This method is called only after StartTick method and m_pending will hold the promise which should be fulfilled. /// void HandleTickError(Exception error) { m_pending.Reject(error); } protected virtual void OnTickError(Exception error) { } protected virtual void OnTickCancel(Exception error) { } /// /// Invoked when the timer ticks, use this method to implement your logic /// 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, Exception lastError) { if (disposing) Safe.Dispose(m_timer); base.Dispose(disposing, lastError); } } }