Mercurial > pub > ImplabNet
diff Implab/Components/PollingComponent.cs @ 203:4d9830a9bbb8 v2
Added 'Fail' method to RunnableComponent which allows component to move from
Running to Failed state.
Added PollingComponent a timer based runnable component
More tests
Added FailPromise a thin class to wrap exceptions
Fixed error handling in SuccessPromise classes.
author | cin |
---|---|
date | Tue, 18 Oct 2016 17:49:54 +0300 |
parents | |
children | 8200ab154c8a |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab/Components/PollingComponent.cs Tue Oct 18 17:49:54 2016 +0300 @@ -0,0 +1,155 @@ +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, Exception lastError) { + if (disposing) + Safe.Dispose(m_timer); + + base.Dispose(disposing, lastError); + } + } +} +