using System; using System.Threading; using System.Threading.Tasks; namespace Implab.Components { public abstract class PollingComponent : RunnableComponent { readonly Timer m_timer; readonly CancellationTokenSource m_cancellation = new CancellationTokenSource(); Task m_pending; Task m_poll; /// /// Poll interval in milliseconds. /// /// public int Interval { get; set; } /// /// Delay to the first poll after start in milliseconds /// /// public int Delay { get; set; } /// /// Indicates how to handle unhandled exceptions in method. /// /// public bool FailOnError { get; set; } /// /// Event for the unhandled exceptions in method. /// public event EventHandler UnhandledException; protected PollingComponent(bool initialized) : base(initialized) { m_timer = new Timer(OnTimer); } protected override void RunInternal() { ScheduleNextPoll(Delay); } protected override async Task StopInternalAsync(CancellationToken ct) { // component in Stopping state, no new polls will be scheduled // we do not need additional synchronization logic here // since RunnableComponent already done this m_cancellation.Cancel(); try { // await for pending poll if (m_poll != null) await m_poll; } catch (OperationCanceledException) { // OK } } protected abstract Task Poll(CancellationToken ct); void ScheduleNextPoll(int timeout) { // access and modification of the component state // in custom methods requires a synchronization lock (SynchronizationObject) { if (State == ExecutionState.Running) { m_pending = Safe.CreateTask(m_cancellation.Token); m_poll = m_pending.Then(() => Poll(m_cancellation.Token)); m_timer.Change(timeout, Timeout.Infinite); } } } async void OnTimer(object state) { try { // changes to m_pending and m_poll are done // only in ScheduleNextPoll method, hence we // can safely use them here m_pending.Start(); await m_poll; // schedule next poll ScheduleNextPoll(Interval); } catch (Exception e) { // hanle error UnhandledException.DispatchEvent(this, new UnhandledExceptionEventArgs(e, false)); if (FailOnError) Fail(e); else ScheduleNextPoll(Interval); } } protected override void Dispose(bool disposing) { if (disposing) Safe.Dispose(m_timer, m_cancellation); base.Dispose(disposing); } } }