diff --git a/Implab/Components/PollingRunnableComponent.cs b/Implab/Components/PollingRunnableComponent.cs new file mode 100644 --- /dev/null +++ b/Implab/Components/PollingRunnableComponent.cs @@ -0,0 +1,98 @@ +using System; +using System.Threading; +using Implab.Diagnostics; + +namespace Implab.Components { + public class PollingRunnableComponent : RunnableComponent { + readonly Timer m_timer; + readonly Func, IPromise> m_dispatcher; + readonly TimeSpan m_interval; + + int m_processing; + Promise m_pending; + + protected PollingRunnableComponent(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 { + AwaitTick(m_dispatcher != null ? m_dispatcher(OnTick) : OnTick()); + } catch (Exception error) { + HandleTickError(error); + } + } + } + + /// + /// Starts the tick handler. + /// + /// 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() { + if (State == ExecutionState.Running && Interlocked.CompareExchange(ref m_processing, 1, 0) == 0) { + m_pending = new Promise(); + m_pending + .On(() => m_processing = 0, PromiseEventType.All) + .On(null, LogTickError); + return true; + } + return false; + } + + protected virtual void AwaitTick(IPromise tick) { + if (tick == null) { + m_pending.Resolve(); + } else { + tick.On( + m_pending.Resolve, + m_pending.Reject, + m_pending.CancelOperation + ); + m_pending.CancellationRequested(tick.Cancel); + } + } + + protected virtual void HandleTickError(Exception error) { + m_pending.Reject(error); + } + + protected virtual void LogTickError(Exception error) { + } + + protected virtual IPromise OnTick() { + return Promise.SUCCESS; + } + + protected override IPromise OnStop() { + m_timer.Change(-1, -1); + + if (m_pending != null) { + m_pending.Cancel(); + return m_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); + } + } +} + diff --git a/Implab/Components/RunnableComponent.cs b/Implab/Components/RunnableComponent.cs --- a/Implab/Components/RunnableComponent.cs +++ b/Implab/Components/RunnableComponent.cs @@ -210,7 +210,8 @@ namespace Implab.Components { public ExecutionState State { get { - return m_stateMachine.State; + lock (m_stateMachine) + return m_stateMachine.State; } } diff --git a/Implab/Implab.csproj b/Implab/Implab.csproj --- a/Implab/Implab.csproj +++ b/Implab/Implab.csproj @@ -194,6 +194,7 @@ +