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 | 
