##// END OF EJS Templates
Слияние с default
Слияние с default

File last commit:

r208:7d07503621fe v2
r224:1ba2127cfcd8 merge v2
Show More
PollingComponent.cs
155 lines | 5.4 KiB | text/x-csharp | CSharpLexer
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) {
if (disposing)
m_timer.Dispose();
base.Dispose(disposing);
}
}
}