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 m_cancellation.Cancel(); try { // await for pending poll await m_poll; } catch (OperationCanceledException e) { // OK } } protected abstract Task Poll(CancellationToken ct); void ScheduleNextPoll(int timeout) { 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 { m_pending.Start(); await m_poll; } catch (Exception e) { UnhandledException.DispatchEvent(this, new UnhandledExceptionEventArgs(e, false)); if (FailOnError) Fail(e); } ScheduleNextPoll(Interval); } protected override void Dispose(bool disposing) { if (disposing) Safe.Dispose(m_timer, m_cancellation); base.Dispose(disposing); } } }