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);
}
}
}