|
|
using System;
|
|
|
using System.Threading;
|
|
|
using Implab.Diagnostics;
|
|
|
|
|
|
namespace Implab.Components {
|
|
|
public class PollingComponent : RunnableComponent {
|
|
|
readonly Timer m_timer;
|
|
|
readonly Func<Func<ICancellationToken, IPromise>, IPromise> m_dispatcher;
|
|
|
readonly TimeSpan m_interval;
|
|
|
|
|
|
readonly object m_lock = new object();
|
|
|
|
|
|
ActionTask m_pending;
|
|
|
|
|
|
protected PollingComponent(TimeSpan interval, Func<Func<ICancellationToken, IPromise>, IPromise> dispatcher, bool initialized) : base(initialized) {
|
|
|
m_timer = new Timer(OnInternalTick);
|
|
|
|
|
|
m_interval = interval;
|
|
|
m_dispatcher = dispatcher;
|
|
|
}
|
|
|
|
|
|
protected override IPromise OnStart() {
|
|
|
m_timer.Change(TimeSpan.Zero, m_interval);
|
|
|
|
|
|
return base.OnStart();
|
|
|
}
|
|
|
|
|
|
void OnInternalTick(object state) {
|
|
|
if (StartTick()) {
|
|
|
try {
|
|
|
if (m_dispatcher != null) {
|
|
|
var result = m_dispatcher(OnTick);
|
|
|
m_pending.CancellationRequested(result.Cancel);
|
|
|
AwaitTick(result);
|
|
|
} else {
|
|
|
AwaitTick(OnTick(m_pending));
|
|
|
}
|
|
|
} catch (Exception error) {
|
|
|
HandleTickError(error);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// Checks wheather there is no running handler in the component and marks that the handler is starting.
|
|
|
/// </summary>
|
|
|
/// <returns>boolean value, true - the new tick handler may be invoked, false - a tick handler is already running or a component isn't running.</returns>
|
|
|
/// <remarks>
|
|
|
/// If the component is stopping no new handlers can be run. Every successful call to this method must be completed with either AwaitTick or HandleTickError handlers.
|
|
|
/// </remarks>
|
|
|
protected virtual bool StartTick() {
|
|
|
lock (m_lock) {
|
|
|
if (State != ExecutionState.Running || m_pending != null)
|
|
|
return false;
|
|
|
// actually the component may be in a not running state here (stopping, disposed or what ever),
|
|
|
// but OnStop method also locks on the same object and will handle operation in m_pending
|
|
|
m_pending = new ActionTask(
|
|
|
() => {
|
|
|
// only one operation is running, it's safe to assing m_pending from it
|
|
|
m_pending = null;
|
|
|
},
|
|
|
ex => {
|
|
|
try {
|
|
|
OnTickError(ex);
|
|
|
// Analysis disable once EmptyGeneralCatchClause
|
|
|
} catch {
|
|
|
} finally {
|
|
|
m_pending = null;
|
|
|
}
|
|
|
// suppress error
|
|
|
},
|
|
|
ex => {
|
|
|
try {
|
|
|
OnTickCancel(ex);
|
|
|
// Analysis disable once EmptyGeneralCatchClause
|
|
|
} catch {
|
|
|
} finally {
|
|
|
m_pending = null;
|
|
|
}
|
|
|
// supress cancellation
|
|
|
},
|
|
|
false
|
|
|
);
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// Awaits the tick.
|
|
|
/// </summary>
|
|
|
/// <param name="tick">Tick.</param>
|
|
|
/// <remarks>
|
|
|
/// This method is called only after StartTick method and m_pending will hold the promise which should be fulfilled.
|
|
|
/// </remarks>
|
|
|
void AwaitTick(IPromise tick) {
|
|
|
if (tick == null) {
|
|
|
m_pending.Resolve();
|
|
|
} else {
|
|
|
tick.On(
|
|
|
m_pending.Resolve,
|
|
|
m_pending.Reject,
|
|
|
m_pending.CancelOperation
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// Handles the tick error.
|
|
|
/// </summary>
|
|
|
/// <remarks>
|
|
|
/// This method is called only after StartTick method and m_pending will hold the promise which should be fulfilled.
|
|
|
/// </remarks>
|
|
|
void HandleTickError(Exception error) {
|
|
|
m_pending.Reject(error);
|
|
|
}
|
|
|
|
|
|
protected virtual void OnTickError(Exception error) {
|
|
|
}
|
|
|
|
|
|
protected virtual void OnTickCancel(Exception error) {
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// Invoked when the timer ticks, use this method to implement your logic
|
|
|
/// </summary>
|
|
|
protected virtual IPromise OnTick(ICancellationToken cancellationToken) {
|
|
|
return Promise.Success;
|
|
|
}
|
|
|
|
|
|
protected override IPromise OnStop() {
|
|
|
m_timer.Change(-1, -1);
|
|
|
|
|
|
// the component is in the stopping state
|
|
|
lock (m_lock) {
|
|
|
// after this lock no more pending operations could be created
|
|
|
var pending = m_pending;
|
|
|
// m_pending could be fulfilled and set to null already
|
|
|
if (pending != null) {
|
|
|
pending.Cancel();
|
|
|
return pending.Then(base.OnStop);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return base.OnStop();
|
|
|
}
|
|
|
|
|
|
protected override void Dispose(bool disposing, Exception lastError) {
|
|
|
if (disposing)
|
|
|
Safe.Dispose(m_timer);
|
|
|
|
|
|
base.Dispose(disposing, lastError);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|