PollingComponent.cs
155 lines
| 5.5 KiB
| text/x-csharp
|
CSharpLexer
cin
|
r203 | 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) { | ||||
cin
|
r205 | return Promise.Success; | ||
cin
|
r203 | } | ||
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); | ||||
} | ||||
} | ||||
} | ||||