Mercurial > pub > ImplabNet
comparison Implab/AbstractEvent.cs @ 243:b1e0ffdf3451 v3
working on promises
author | cin |
---|---|
date | Wed, 24 Jan 2018 19:24:10 +0300 |
parents | cbe10ac0731e |
children | eee3e49dd1ff |
comparison
equal
deleted
inserted
replaced
242:cbe10ac0731e | 243:b1e0ffdf3451 |
---|---|
3 using System.Threading; | 3 using System.Threading; |
4 using System.Reflection; | 4 using System.Reflection; |
5 using System.Diagnostics; | 5 using System.Diagnostics; |
6 | 6 |
7 namespace Implab { | 7 namespace Implab { |
8 /// <summary> | |
9 /// Abstract class for creation of custom one-shot thread safe events. | |
10 /// </summary> | |
11 /// <remarks> | |
12 /// <para> | |
13 /// An event is something that should happen in the future and the | |
14 /// triggering of the event causes execution of some pending actions | |
15 /// which are formely event handlers. One-shot events occur only once | |
16 /// and any handler added after the event is triggered should run | |
17 /// without a delay. | |
18 /// </para> | |
19 /// <para> | |
20 /// The lifecycle of the one-shot event is tipically consists of following | |
21 /// phases. | |
22 /// <list> | |
23 /// <description>Pending state. This is the initial state of the event. Any | |
24 /// handler added to the event will be queued for the future execution. | |
25 /// </description> | |
26 /// <description>Transitional state. This is intermediate state between pending | |
27 /// and fulfilled states, during this state internal initialization and storing | |
28 /// of the result occurs. | |
29 /// </description> | |
30 /// <description>Fulfilled state. The event contains the result, all queued | |
31 /// handlers are signalled to run and newly added handlers are executed | |
32 /// immediatelly. | |
33 /// </description> | |
34 /// </list> | |
35 /// </para> | |
36 /// </remarks> | |
8 public abstract class AbstractEvent<THandler> where THandler : class { | 37 public abstract class AbstractEvent<THandler> where THandler : class { |
38 const int PENDING_SATE = 0; | |
9 | 39 |
10 const int PENDING_SATE = 0; | 40 const int TRANSITIONAL_STATE = 1; |
11 protected const int TRANSITIONAL_STATE = 1; | |
12 | 41 |
13 protected const int SUCCEEDED_STATE = 2; | 42 const int FULFILLED_STATE = 2; |
14 protected const int REJECTED_STATE = 3; | |
15 | 43 |
16 volatile int m_state; | 44 volatile int m_state; |
17 Exception m_error; | |
18 | 45 |
19 THandler m_handler; | 46 THandler m_handler; |
20 SimpleAsyncQueue<THandler> m_extraHandlers; | 47 SimpleAsyncQueue<THandler> m_extraHandlers; |
48 | |
49 public bool IsFulfilled { | |
50 get { | |
51 return m_state > TRANSITIONAL_STATE; | |
52 } | |
53 } | |
21 | 54 |
22 #region state managment | 55 #region state managment |
23 protected bool BeginTransit() { | 56 protected bool BeginTransit() { |
24 return PENDING_SATE == Interlocked.CompareExchange(ref m_state, TRANSITIONAL_STATE, PENDING_SATE); | 57 return PENDING_SATE == Interlocked.CompareExchange(ref m_state, TRANSITIONAL_STATE, PENDING_SATE); |
25 } | 58 } |
26 | 59 |
27 protected void CompleteTransit(int state) { | 60 protected void CompleteTransit() { |
28 #if DEBUG | 61 #if DEBUG |
29 if (TRANSITIONAL_STATE != Interlocked.CompareExchange(ref m_state, state, TRANSITIONAL_STATE)) | 62 if (TRANSITIONAL_STATE != Interlocked.CompareExchange(ref m_state, FULFILLED_STATE, TRANSITIONAL_STATE)) |
30 throw new InvalidOperationException("Can't complete transition when the object isn't in the transitional state"); | 63 throw new InvalidOperationException("Can't complete transition when the object isn't in the transitional state"); |
31 #else | 64 #else |
32 m_state = state; | 65 m_state = state; |
33 #endif | 66 #endif |
34 Signal(); | 67 Signal(); |
41 spin.SpinOnce(); | 74 spin.SpinOnce(); |
42 } while (m_state == TRANSITIONAL_STATE); | 75 } while (m_state == TRANSITIONAL_STATE); |
43 } | 76 } |
44 } | 77 } |
45 | 78 |
46 protected bool BeginSetResult() { | |
47 if (!BeginTransit()) { | |
48 WaitTransition(); | |
49 return false; | |
50 } | |
51 return true; | |
52 } | |
53 | 79 |
54 protected void EndSetResult() { | 80 protected abstract void SignalHandler(THandler handler); |
55 CompleteTransit(SUCCEEDED_STATE); | |
56 } | |
57 | |
58 | |
59 | |
60 /// <summary> | |
61 /// Выполняет обещание, сообщая об ошибке | |
62 /// </summary> | |
63 /// <remarks> | |
64 /// Поскольку обещание должно работать в многопточной среде, при его выполнении сразу несколько потоков | |
65 /// могу вернуть ошибку, при этом только первая будет использована в качестве результата, остальные | |
66 /// будут проигнорированы. | |
67 /// </remarks> | |
68 /// <param name="error">Исключение возникшее при выполнении операции</param> | |
69 /// <exception cref="InvalidOperationException">Данное обещание уже выполнено</exception> | |
70 protected void SetError(Exception error) { | |
71 if (BeginTransit()) { | |
72 m_error = error; | |
73 CompleteTransit(REJECTED_STATE); | |
74 } else { | |
75 WaitTransition(); | |
76 if (m_state == SUCCEEDED_STATE) | |
77 throw new InvalidOperationException("The promise is already resolved"); | |
78 } | |
79 } | |
80 | |
81 protected abstract void SignalHandler(THandler handler, int signal); | |
82 | 81 |
83 void Signal() { | 82 void Signal() { |
84 THandler handler; | 83 THandler handler; |
85 while (TryDequeueHandler(out handler)) | 84 while (TryDequeueHandler(out handler)) |
86 SignalHandler(handler, m_state); | 85 SignalHandler(handler); |
87 } | 86 } |
88 | 87 |
89 #endregion | 88 #endregion |
90 | 89 |
91 protected abstract Signal GetFulfillSignal(); | 90 protected abstract Signal GetFulfillSignal(); |
92 | 91 |
93 #region synchronization traits | 92 #region synchronization traits |
94 protected void WaitResult(int timeout) { | 93 protected void WaitResult(int timeout) { |
95 if (!(IsFulfilled || GetFulfillSignal().Wait(timeout))) | 94 if (!(IsFulfilled || GetFulfillSignal().Wait(timeout))) |
96 throw new TimeoutException(); | 95 throw new TimeoutException(); |
97 | |
98 if (IsRejected) | |
99 Rethrow(); | |
100 } | 96 } |
101 | 97 |
102 protected void Rethrow() { | 98 |
103 Debug.Assert(m_error != null); | |
104 if (m_error is OperationCanceledException) | |
105 throw new OperationCanceledException("Operation cancelled", m_error); | |
106 else | |
107 throw new TargetInvocationException(m_error); | |
108 } | |
109 #endregion | 99 #endregion |
110 | 100 |
111 #region handlers managment | 101 #region handlers managment |
112 | 102 |
113 protected void AddHandler(THandler handler) { | 103 protected void AddHandler(THandler handler) { |
114 | 104 |
115 if (m_state > 1) { | 105 if (IsFulfilled) { |
116 // the promise is in the resolved state, just invoke the handler | 106 // the promise is in the resolved state, just invoke the handler |
117 SignalHandler(handler, m_state); | 107 SignalHandler(handler); |
118 } else { | 108 } else { |
119 if (Interlocked.CompareExchange(ref m_handler, handler, null) != null) { | 109 EnqueueHandler(handler); |
120 if (m_extraHandlers == null) | |
121 // compare-exchange will fprotect from loosing already created queue | |
122 Interlocked.CompareExchange(ref m_extraHandlers, new SimpleAsyncQueue<THandler>(), null); | |
123 m_extraHandlers.Enqueue(handler); | |
124 } | |
125 | 110 |
126 if (m_state > 1 && TryDequeueHandler(out handler)) | 111 if (IsFulfilled && TryDequeueHandler(out handler)) |
127 // if the promise have been resolved while we was adding the handler to the queue | 112 // if the promise have been resolved while we was adding the handler to the queue |
128 // we can't guarantee that someone is still processing it | 113 // we can't guarantee that someone is still processing it |
129 // therefore we need to fetch a handler from the queue and execute it | 114 // therefore we need to fetch a handler from the queue and execute it |
130 // note that fetched handler may be not the one that we have added | 115 // note that fetched handler may be not the one that we have added |
131 // even we can fetch no handlers at all :) | 116 // even we can fetch no handlers at all :) |
132 SignalHandler(handler, m_state); | 117 SignalHandler(handler); |
133 } | 118 } |
134 | 119 |
120 } | |
121 | |
122 void EnqueueHandler(THandler handler) { | |
123 if (Interlocked.CompareExchange(ref m_handler, handler, null) != null) { | |
124 if (m_extraHandlers == null) | |
125 // compare-exchange will protect from loosing already created queue | |
126 Interlocked.CompareExchange(ref m_extraHandlers, new SimpleAsyncQueue<THandler>(), null); | |
127 m_extraHandlers.Enqueue(handler); | |
128 } | |
135 } | 129 } |
136 | 130 |
137 bool TryDequeueHandler(out THandler handler) { | 131 bool TryDequeueHandler(out THandler handler) { |
138 handler = Interlocked.Exchange(ref m_handler, null); | 132 handler = Interlocked.Exchange(ref m_handler, null); |
139 if (handler != null) | 133 if (handler != null) |
140 return true; | 134 return true; |
141 return m_extraHandlers != null && m_extraHandlers.TryDequeue(out handler); | 135 return m_extraHandlers != null && m_extraHandlers.TryDequeue(out handler); |
142 } | 136 } |
143 | 137 |
144 #endregion | 138 #endregion |
145 | |
146 #region IPromise implementation | |
147 | |
148 public bool IsFulfilled { | |
149 get { | |
150 return m_state > TRANSITIONAL_STATE; | |
151 } | |
152 } | |
153 | |
154 public bool IsRejected { | |
155 get { | |
156 return m_state == REJECTED_STATE; | |
157 } | |
158 } | |
159 | |
160 #endregion | |
161 | |
162 public Exception RejectReason { | |
163 get { | |
164 return m_error; | |
165 } | |
166 } | |
167 | |
168 } | 139 } |
169 } | 140 } |
170 | 141 |