Mercurial > pub > ImplabNet
comparison Implab/Components/PollingComponent.cs @ 203:4d9830a9bbb8 v2
Added 'Fail' method to RunnableComponent which allows component to move from
Running to Failed state.
Added PollingComponent a timer based runnable component
More tests
Added FailPromise a thin class to wrap exceptions
Fixed error handling in SuccessPromise classes.
| author | cin |
|---|---|
| date | Tue, 18 Oct 2016 17:49:54 +0300 |
| parents | |
| children | 8200ab154c8a |
comparison
equal
deleted
inserted
replaced
| 202:2651cb9a4250 | 203:4d9830a9bbb8 |
|---|---|
| 1 using System; | |
| 2 using System.Threading; | |
| 3 using Implab.Diagnostics; | |
| 4 | |
| 5 namespace Implab.Components { | |
| 6 public class PollingComponent : RunnableComponent { | |
| 7 readonly Timer m_timer; | |
| 8 readonly Func<Func<ICancellationToken, IPromise>, IPromise> m_dispatcher; | |
| 9 readonly TimeSpan m_interval; | |
| 10 | |
| 11 readonly object m_lock = new object(); | |
| 12 | |
| 13 ActionTask m_pending; | |
| 14 | |
| 15 protected PollingComponent(TimeSpan interval, Func<Func<ICancellationToken, IPromise>, IPromise> dispatcher, bool initialized) : base(initialized) { | |
| 16 m_timer = new Timer(OnInternalTick); | |
| 17 | |
| 18 m_interval = interval; | |
| 19 m_dispatcher = dispatcher; | |
| 20 } | |
| 21 | |
| 22 protected override IPromise OnStart() { | |
| 23 m_timer.Change(TimeSpan.Zero, m_interval); | |
| 24 | |
| 25 return base.OnStart(); | |
| 26 } | |
| 27 | |
| 28 void OnInternalTick(object state) { | |
| 29 if (StartTick()) { | |
| 30 try { | |
| 31 if (m_dispatcher != null) { | |
| 32 var result = m_dispatcher(OnTick); | |
| 33 m_pending.CancellationRequested(result.Cancel); | |
| 34 AwaitTick(result); | |
| 35 } else { | |
| 36 AwaitTick(OnTick(m_pending)); | |
| 37 } | |
| 38 } catch (Exception error) { | |
| 39 HandleTickError(error); | |
| 40 } | |
| 41 } | |
| 42 } | |
| 43 | |
| 44 /// <summary> | |
| 45 /// Checks wheather there is no running handler in the component and marks that the handler is starting. | |
| 46 /// </summary> | |
| 47 /// <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> | |
| 48 /// <remarks> | |
| 49 /// 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. | |
| 50 /// </remarks> | |
| 51 protected virtual bool StartTick() { | |
| 52 lock (m_lock) { | |
| 53 if (State != ExecutionState.Running || m_pending != null) | |
| 54 return false; | |
| 55 // actually the component may be in a not running state here (stopping, disposed or what ever), | |
| 56 // but OnStop method also locks on the same object and will handle operation in m_pending | |
| 57 m_pending = new ActionTask( | |
| 58 () => { | |
| 59 // only one operation is running, it's safe to assing m_pending from it | |
| 60 m_pending = null; | |
| 61 }, | |
| 62 ex => { | |
| 63 try { | |
| 64 OnTickError(ex); | |
| 65 // Analysis disable once EmptyGeneralCatchClause | |
| 66 } catch { | |
| 67 } finally { | |
| 68 m_pending = null; | |
| 69 } | |
| 70 // suppress error | |
| 71 }, | |
| 72 ex => { | |
| 73 try { | |
| 74 OnTickCancel(ex); | |
| 75 // Analysis disable once EmptyGeneralCatchClause | |
| 76 } catch { | |
| 77 } finally { | |
| 78 m_pending = null; | |
| 79 } | |
| 80 // supress cancellation | |
| 81 }, | |
| 82 false | |
| 83 ); | |
| 84 return true; | |
| 85 } | |
| 86 } | |
| 87 | |
| 88 /// <summary> | |
| 89 /// Awaits the tick. | |
| 90 /// </summary> | |
| 91 /// <param name="tick">Tick.</param> | |
| 92 /// <remarks> | |
| 93 /// This method is called only after StartTick method and m_pending will hold the promise which should be fulfilled. | |
| 94 /// </remarks> | |
| 95 void AwaitTick(IPromise tick) { | |
| 96 if (tick == null) { | |
| 97 m_pending.Resolve(); | |
| 98 } else { | |
| 99 tick.On( | |
| 100 m_pending.Resolve, | |
| 101 m_pending.Reject, | |
| 102 m_pending.CancelOperation | |
| 103 ); | |
| 104 } | |
| 105 } | |
| 106 | |
| 107 /// <summary> | |
| 108 /// Handles the tick error. | |
| 109 /// </summary> | |
| 110 /// <remarks> | |
| 111 /// This method is called only after StartTick method and m_pending will hold the promise which should be fulfilled. | |
| 112 /// </remarks> | |
| 113 void HandleTickError(Exception error) { | |
| 114 m_pending.Reject(error); | |
| 115 } | |
| 116 | |
| 117 protected virtual void OnTickError(Exception error) { | |
| 118 } | |
| 119 | |
| 120 protected virtual void OnTickCancel(Exception error) { | |
| 121 } | |
| 122 | |
| 123 /// <summary> | |
| 124 /// Invoked when the timer ticks, use this method to implement your logic | |
| 125 /// </summary> | |
| 126 protected virtual IPromise OnTick(ICancellationToken cancellationToken) { | |
| 127 return Promise.SUCCESS; | |
| 128 } | |
| 129 | |
| 130 protected override IPromise OnStop() { | |
| 131 m_timer.Change(-1, -1); | |
| 132 | |
| 133 // the component is in the stopping state | |
| 134 lock (m_lock) { | |
| 135 // after this lock no more pending operations could be created | |
| 136 var pending = m_pending; | |
| 137 // m_pending could be fulfilled and set to null already | |
| 138 if (pending != null) { | |
| 139 pending.Cancel(); | |
| 140 return pending.Then(base.OnStop); | |
| 141 } | |
| 142 } | |
| 143 | |
| 144 return base.OnStop(); | |
| 145 } | |
| 146 | |
| 147 protected override void Dispose(bool disposing, Exception lastError) { | |
| 148 if (disposing) | |
| 149 Safe.Dispose(m_timer); | |
| 150 | |
| 151 base.Dispose(disposing, lastError); | |
| 152 } | |
| 153 } | |
| 154 } | |
| 155 |
