PollingComponent.cs
155 lines
| 5.4 KiB
| text/x-csharp
|
CSharpLexer
|
|
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) { | ||||
|
|
r205 | return Promise.Success; | ||
|
|
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(); | ||||
| } | ||||
|
|
r208 | protected override void Dispose(bool disposing) { | ||
|
|
r203 | if (disposing) | ||
|
|
r208 | m_timer.Dispose(); | ||
|
|
r203 | |||
|
|
r208 | base.Dispose(disposing); | ||
|
|
r203 | } | ||
| } | ||||
| } | ||||
