|
|
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;
|
|
|
|
|
|
/// <summary>
|
|
|
/// Poll interval in milliseconds.
|
|
|
/// </summary>
|
|
|
/// <returns></returns>
|
|
|
public int Interval { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// Delay to the first poll after start in milliseconds
|
|
|
/// </summary>
|
|
|
/// <returns></returns>
|
|
|
public int Delay { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// Indicates how to handle unhandled exceptions in <see cref="Poll()"/> method.
|
|
|
/// </summary>
|
|
|
/// <returns></returns>
|
|
|
public bool FailOnError { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// Event for the unhandled exceptions in <see cref="Poll()"/> method.
|
|
|
/// </summary>
|
|
|
public event EventHandler<UnhandledExceptionEventArgs> 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);
|
|
|
}
|
|
|
|
|
|
}
|
|
|
}
|