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 |