PollingComponent.cs
106 lines
| 3.3 KiB
| text/x-csharp
|
CSharpLexer
cin
|
r258 | 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(); | |||
cin
|
r259 | Task m_pending; | |
Task m_poll; | |||
cin
|
r258 | /// <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); | |||
} | |||
cin
|
r259 | protected override async Task StopInternalAsync(CancellationToken ct) { | |
// component in Stopping state, no new polls will be scheduled | |||
cin
|
r284 | ||
// we do not need additional synchronization logic here | |||
// since RunnableComponent already done this | |||
cin
|
r259 | m_cancellation.Cancel(); | |
try { | |||
// await for pending poll | |||
cin
|
r262 | if (m_poll != null) | |
await m_poll; | |||
cin
|
r260 | } catch (OperationCanceledException) { | |
cin
|
r259 | // OK | |
} | |||
} | |||
cin
|
r258 | ||
protected abstract Task Poll(CancellationToken ct); | |||
void ScheduleNextPoll(int timeout) { | |||
cin
|
r284 | // access and modification of the component state | |
// in custom methods requires a synchronization | |||
cin
|
r258 | lock (SynchronizationObject) { | |
cin
|
r284 | ||
cin
|
r259 | if (State == ExecutionState.Running) { | |
m_pending = Safe.CreateTask(m_cancellation.Token); | |||
m_poll = m_pending.Then(() => Poll(m_cancellation.Token)); | |||
cin
|
r258 | m_timer.Change(timeout, Timeout.Infinite); | |
cin
|
r259 | } | |
cin
|
r258 | } | |
} | |||
cin
|
r259 | async void OnTimer(object state) { | |
cin
|
r258 | try { | |
cin
|
r284 | // changes to m_pending and m_poll are done | |
// only in ScheduleNextPoll method, hence we | |||
// can safely use them here | |||
cin
|
r259 | m_pending.Start(); | |
await m_poll; | |||
cin
|
r284 | ||
// schedule next poll | |||
cin
|
r260 | ScheduleNextPoll(Interval); | |
cin
|
r258 | } catch (Exception e) { | |
cin
|
r284 | // hanle error | |
cin
|
r258 | UnhandledException.DispatchEvent(this, new UnhandledExceptionEventArgs(e, false)); | |
cin
|
r260 | ||
cin
|
r258 | if (FailOnError) | |
Fail(e); | |||
cin
|
r260 | else | |
ScheduleNextPoll(Interval); | |||
cin
|
r258 | } | |
cin
|
r260 | ||
cin
|
r258 | } | |
protected override void Dispose(bool disposing) { | |||
if (disposing) | |||
Safe.Dispose(m_timer, m_cancellation); | |||
base.Dispose(disposing); | |||
} | |||
} | |||
} |