1 #pragma warning disable 0420
4 // Copyright (c) Microsoft Corporation. All rights reserved.
7 // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
9 // SlimManualResetEvent.cs
11 // <OWNER>Microsoft</OWNER>
13 // An manual-reset event that mixes a little spinning with a true Win32 event.
15 // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
18 using System
.Diagnostics
;
19 using System
.Security
.Permissions
;
20 using System
.Threading
;
21 using System
.Runtime
.InteropServices
;
22 using System
.Diagnostics
.Contracts
;
24 namespace System
.Threading
27 // ManualResetEventSlim wraps a manual-reset event internally with a little bit of
28 // spinning. When an event will be set imminently, it is often advantageous to avoid
29 // a 4k+ cycle context switch in favor of briefly spinning. Therefore we layer on to
30 // a brief amount of spinning that should, on the average, make using the slim event
31 // cheaper than using Win32 events directly. This can be reset manually, much like
32 // a Win32 manual-reset would be.
35 // We lazily allocate the Win32 event internally. Therefore, the caller should
36 // always call Dispose to clean it up, just in case. This API is a no-op of the
37 // event wasn't allocated, but if it was, ensures that the event goes away
38 // eagerly, instead of waiting for finalization.
41 /// Provides a slimmed down version of <see cref="T:System.Threading.ManualResetEvent"/>.
44 /// All public and protected members of <see cref="ManualResetEventSlim"/> are thread-safe and may be used
45 /// concurrently from multiple threads, with the exception of Dispose, which
46 /// must only be used when all other operations on the <see cref="ManualResetEventSlim"/> have
47 /// completed, and Reset, which should only be used when no other threads are
48 /// accessing the event.
51 [DebuggerDisplay("Set = {IsSet}")]
52 [HostProtection(Synchronization
= true, ExternalThreading
= true)]
53 public class ManualResetEventSlim
: IDisposable
55 // These are the default spin counts we use on single-proc and MP machines.
56 private const int DEFAULT_SPIN_SP
= 1;
57 private const int DEFAULT_SPIN_MP
= SpinWait
.YIELD_THRESHOLD
;
59 private volatile object m_lock
;
60 // A lock used for waiting and pulsing. Lazily initialized via EnsureLockObjectCreated()
62 private volatile ManualResetEvent m_eventObj
; // A true Win32 event used for waiting.
65 //For a packed word a uint would seem better, but Interlocked.* doesn't support them as uint isn't CLS-compliant.
66 private volatile int m_combinedState
; //ie a UInt32. Used for the state items listed below.
68 //1-bit for signalled state
69 private const int SignalledState_BitMask
= unchecked((int)0x80000000);//1000 0000 0000 0000 0000 0000 0000 0000
70 private const int SignalledState_ShiftCount
= 31;
72 //1-bit for disposed state
73 private const int Dispose_BitMask
= unchecked((int)0x40000000);//0100 0000 0000 0000 0000 0000 0000 0000
75 //11-bits for m_spinCount
76 private const int SpinCountState_BitMask
= unchecked((int)0x3FF80000); //0011 1111 1111 1000 0000 0000 0000 0000
77 private const int SpinCountState_ShiftCount
= 19;
78 private const int SpinCountState_MaxValue
= (1 << 11) - 1; //2047
80 //19-bits for m_waiters. This allows support of 512K threads waiting which should be ample
81 private const int NumWaitersState_BitMask
= unchecked((int)0x0007FFFF); // 0000 0000 0000 0111 1111 1111 1111 1111
82 private const int NumWaitersState_ShiftCount
= 0;
83 private const int NumWaitersState_MaxValue
= (1 << 19) - 1; //512K-1
87 private static int s_nextId
; // The next id that will be given out.
88 private int m_id
= Interlocked
.Increment(ref s_nextId
); // A unique id for debugging purposes only.
89 private long m_lastSetTime
;
90 private long m_lastResetTime
;
94 /// Gets the underlying <see cref="T:System.Threading.WaitHandle"/> object for this <see
95 /// cref="ManualResetEventSlim"/>.
97 /// <value>The underlying <see cref="T:System.Threading.WaitHandle"/> event object fore this <see
98 /// cref="ManualResetEventSlim"/>.</value>
100 /// Accessing this property forces initialization of an underlying event object if one hasn't
101 /// already been created. To simply wait on this <see cref="ManualResetEventSlim"/>,
102 /// the public Wait methods should be preferred.
104 public WaitHandle WaitHandle
110 if (m_eventObj
== null)
112 // Lazily initialize the event object if needed.
113 LazyInitializeEvent();
121 /// Gets whether the event is set.
123 /// <value>true if the event has is set; otherwise, false.</value>
128 return 0 != ExtractStatePortion(m_combinedState
, SignalledState_BitMask
);
133 UpdateStateAtomically(((value) ? 1 : 0) << SignalledState_ShiftCount
, SignalledState_BitMask
);
138 /// Gets the number of spin waits that will be occur before falling back to a true wait.
144 return ExtractStatePortionAndShiftRight(m_combinedState
, SpinCountState_BitMask
, SpinCountState_ShiftCount
);
149 Contract
.Assert(value >= 0, "SpinCount is a restricted-width integer. The value supplied is outside the legal range.");
150 Contract
.Assert(value <= SpinCountState_MaxValue
, "SpinCount is a restricted-width integer. The value supplied is outside the legal range.");
151 // Don't worry about thread safety because it's set one time from the constructor
152 m_combinedState
= (m_combinedState
& ~SpinCountState_BitMask
) | (value << SpinCountState_ShiftCount
);
157 /// How many threads are waiting.
163 return ExtractStatePortionAndShiftRight(m_combinedState
, NumWaitersState_BitMask
, NumWaitersState_ShiftCount
);
168 //setting to <0 would indicate an internal flaw, hence Assert is appropriate.
169 Contract
.Assert(value >= 0, "NumWaiters should never be less than zero. This indicates an internal error.");
171 // it is possible for the max number of waiters to be exceeded via user-code, hence we use a real exception here.
172 if (value >= NumWaitersState_MaxValue
)
173 throw new InvalidOperationException(String
.Format(Environment
.GetResourceString("ManualResetEventSlim_ctor_TooManyWaiters"), NumWaitersState_MaxValue
));
175 UpdateStateAtomically(value << NumWaitersState_ShiftCount
, NumWaitersState_BitMask
);
180 //-----------------------------------------------------------------------------------
181 // Constructs a new event, optionally specifying the initial state and spin count.
182 // The defaults are that the event is unsignaled and some reasonable default spin.
186 /// Initializes a new instance of the <see cref="ManualResetEventSlim"/>
187 /// class with an initial state of nonsignaled.
189 public ManualResetEventSlim()
196 /// Initializes a new instance of the <see cref="ManualResetEventSlim"/>
197 /// class with a Boolen value indicating whether to set the intial state to signaled.
199 /// <param name="initialState">true to set the initial state signaled; false to set the initial state
200 /// to nonsignaled.</param>
201 public ManualResetEventSlim(bool initialState
)
203 // Specify the defualt spin count, and use default spin if we're
204 // on a multi-processor machine. Otherwise, we won't.
205 Initialize(initialState
, DEFAULT_SPIN_MP
);
209 /// Initializes a new instance of the <see cref="ManualResetEventSlim"/>
210 /// class with a Boolen value indicating whether to set the intial state to signaled and a specified
213 /// <param name="initialState">true to set the initial state to signaled; false to set the initial state
214 /// to nonsignaled.</param>
215 /// <param name="spinCount">The number of spin waits that will occur before falling back to a true
217 /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="spinCount"/> is less than
218 /// 0 or greater than the maximum allowed value.</exception>
219 public ManualResetEventSlim(bool initialState
, int spinCount
)
223 throw new ArgumentOutOfRangeException("spinCount");
226 if (spinCount
> SpinCountState_MaxValue
)
228 throw new ArgumentOutOfRangeException(
230 String
.Format(Environment
.GetResourceString("ManualResetEventSlim_ctor_SpinCountOutOfRange"), SpinCountState_MaxValue
));
233 // We will suppress default spin because the user specified a count.
234 Initialize(initialState
, spinCount
);
238 /// Initializes the internal state of the event.
240 /// <param name="initialState">Whether the event is set initially or not.</param>
241 /// <param name="spinCount">The spin count that decides when the event will block.</param>
242 private void Initialize(bool initialState
, int spinCount
)
244 this.m_combinedState
= initialState
? (1 << SignalledState_ShiftCount
) : 0;
245 //the spinCount argument has been validated by the ctors.
246 //but we now sanity check our predefined constants.
247 Contract
.Assert(DEFAULT_SPIN_SP
>= 0, "Internal error - DEFAULT_SPIN_SP is outside the legal range.");
248 Contract
.Assert(DEFAULT_SPIN_SP
<= SpinCountState_MaxValue
, "Internal error - DEFAULT_SPIN_SP is outside the legal range.");
250 SpinCount
= PlatformHelper
.IsSingleProcessor
? DEFAULT_SPIN_SP
: spinCount
;
255 /// Helper to ensure the lock object is created before first use.
257 private void EnsureLockObjectCreated()
259 Contract
.Ensures(m_lock
!= null);
264 object newObj
= new object();
265 Interlocked
.CompareExchange(ref m_lock
, newObj
, null); // failure is benign.. someone else won the ----.
269 /// This method lazily initializes the event object. It uses CAS to guarantee that
270 /// many threads racing to call this at once don't result in more than one event
271 /// being stored and used. The event will be signaled or unsignaled depending on
272 /// the state of the thin-event itself, with synchronization taken into account.
274 /// <returns>True if a new event was created and stored, false otherwise.</returns>
275 private bool LazyInitializeEvent()
277 bool preInitializeIsSet
= IsSet
;
278 ManualResetEvent newEventObj
= new ManualResetEvent(preInitializeIsSet
);
280 // We have to CAS this in case we are racing with another thread. We must
281 // guarantee only one event is actually stored in this field.
282 if (Interlocked
.CompareExchange(ref m_eventObj
, newEventObj
, null) != null)
284 // We raced with someone else and lost. Destroy the garbage event.
292 // Now that the event is published, verify that the state hasn't changed since
293 // we snapped the preInitializeState. Another thread could have done that
294 // between our initial observation above and here. The barrier incurred from
295 // the CAS above (in addition to m_state being volatile) prevents this read
296 // from moving earlier and being collapsed with our original one.
297 bool currentIsSet
= IsSet
;
298 if (currentIsSet
!= preInitializeIsSet
)
300 Contract
.Assert(currentIsSet
,
301 "The only safe concurrent transition is from unset->set: detected set->unset.");
303 // We saw it as unsignaled, but it has since become set.
306 // If our event hasn't already been disposed of, we must set it.
307 if (m_eventObj
== newEventObj
)
319 /// Sets the state of the event to signaled, which allows one or more threads waiting on the event to
328 /// Private helper to actually perform the Set.
330 /// <param name="duringCancellation">Indicates whether we are calling Set() during cancellation.</param>
331 /// <exception cref="T:System.OperationCanceledException">The object has been canceled.</exception>
332 private void Set(bool duringCancellation
)
334 // We need to ensure that IsSet=true does not get reordered past the read of m_eventObj
335 // This would be a legal movement according to the .NET memory model.
336 // The code is safe as IsSet involves an Interlocked.CompareExchange which provides a full memory barrier.
339 // If there are waiting threads, we need to pulse them.
342 Contract
.Assert(m_lock
!= null); //if waiters>0, then m_lock has already been created.
346 Monitor
.PulseAll(m_lock
);
350 ManualResetEvent eventObj
= m_eventObj
;
352 //Design-decision: do not set the event if we are in cancellation -> better to deadlock than to wake up waiters incorrectly
353 //It would be preferable to wake up the event and have it throw OCE. This requires MRE to implement cancellation logic
355 if (eventObj
!= null && !duringCancellation
)
357 // We must surround this call to Set in a lock. The reason is fairly subtle.
358 // Sometimes a thread will issue a Wait and wake up after we have set m_state,
359 // but before we have gotten around to setting m_eventObj (just below). That's
360 // because Wait first checks m_state and will only access the event if absolutely
361 // necessary. However, the coding pattern { event.Wait(); event.Dispose() } is
362 // quite common, and we must support it. If the waiter woke up and disposed of
363 // the event object before the setter has finished, however, we would try to set a
364 // now-disposed Win32 event. Crash! To deal with this ----, we use a lock to
365 // protect access to the event object when setting and disposing of it. We also
366 // double-check that the event has not become null in the meantime when in the lock.
370 if (m_eventObj
!= null)
372 // If somebody is waiting, we must set the event.
379 m_lastSetTime
= DateTime
.Now
.Ticks
;
384 /// Sets the state of the event to nonsignaled, which causes threads to block.
387 /// Unlike most of the members of <see cref="ManualResetEventSlim"/>, <see cref="Reset()"/> is not
388 /// thread-safe and may not be used concurrently with other members of this instance.
393 // If there's an event, reset it.
394 if (m_eventObj
!= null)
399 // There is a ---- here. If another thread Sets the event, we will get into a state
400 // where m_state will be unsignaled, yet the Win32 event object will have been signaled.
401 // This could cause waiting threads to wake up even though the event is in an
402 // unsignaled state. This is fine -- those that are calling Reset concurrently are
403 // responsible for doing "the right thing" -- e.g. rechecking the condition and
404 // resetting the event manually.
406 // And finally set our state back to unsignaled.
410 m_lastResetTime
= DateTime
.Now
.Ticks
;
415 /// Blocks the current thread until the current <see cref="ManualResetEventSlim"/> is set.
417 /// <exception cref="T:System.InvalidOperationException">
418 /// The maximum number of waiters has been exceeded.
421 /// The caller of this method blocks indefinitely until the current instance is set. The caller will
422 /// return immediately if the event is currently in a set state.
426 Wait(Timeout
.Infinite
, new CancellationToken());
430 /// Blocks the current thread until the current <see cref="ManualResetEventSlim"/> receives a signal,
431 /// while observing a <see cref="T:System.Threading.CancellationToken"/>.
433 /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> to
435 /// <exception cref="T:System.InvalidOperationException">
436 /// The maximum number of waiters has been exceeded.
438 /// <exception cref="T:System.OperationCanceledExcepton"><paramref name="cancellationToken"/> was
439 /// canceled.</exception>
441 /// The caller of this method blocks indefinitely until the current instance is set. The caller will
442 /// return immediately if the event is currently in a set state.
444 public void Wait(CancellationToken cancellationToken
)
446 Wait(Timeout
.Infinite
, cancellationToken
);
450 /// Blocks the current thread until the current <see cref="ManualResetEventSlim"/> is set, using a
451 /// <see cref="T:System.TimeSpan"/> to measure the time interval.
453 /// <param name="timeout">A <see cref="System.TimeSpan"/> that represents the number of milliseconds
454 /// to wait, or a <see cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely.
456 /// <returns>true if the <see cref="System.Threading.ManualResetEventSlim"/> was set; otherwise,
458 /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="timeout"/> is a negative
459 /// number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater
460 /// than <see cref="System.Int32.MaxValue"/>.</exception>
461 /// <exception cref="T:System.InvalidOperationException">
462 /// The maximum number of waiters has been exceeded.
464 public bool Wait(TimeSpan timeout
)
466 long totalMilliseconds
= (long)timeout
.TotalMilliseconds
;
467 if (totalMilliseconds
< -1 || totalMilliseconds
> int.MaxValue
)
469 throw new ArgumentOutOfRangeException("timeout");
472 return Wait((int)totalMilliseconds
, new CancellationToken());
476 /// Blocks the current thread until the current <see cref="ManualResetEventSlim"/> is set, using a
477 /// <see cref="T:System.TimeSpan"/> to measure the time interval, while observing a <see
478 /// cref="T:System.Threading.CancellationToken"/>.
480 /// <param name="timeout">A <see cref="System.TimeSpan"/> that represents the number of milliseconds
481 /// to wait, or a <see cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely.
483 /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> to
485 /// <returns>true if the <see cref="System.Threading.ManualResetEventSlim"/> was set; otherwise,
487 /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="timeout"/> is a negative
488 /// number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater
489 /// than <see cref="System.Int32.MaxValue"/>.</exception>
490 /// <exception cref="T:System.Threading.OperationCanceledException"><paramref
491 /// name="cancellationToken"/> was canceled.</exception>
492 /// <exception cref="T:System.InvalidOperationException">
493 /// The maximum number of waiters has been exceeded.
495 public bool Wait(TimeSpan timeout
, CancellationToken cancellationToken
)
497 long totalMilliseconds
= (long)timeout
.TotalMilliseconds
;
498 if (totalMilliseconds
< -1 || totalMilliseconds
> int.MaxValue
)
500 throw new ArgumentOutOfRangeException("timeout");
503 return Wait((int)totalMilliseconds
, cancellationToken
);
507 /// Blocks the current thread until the current <see cref="ManualResetEventSlim"/> is set, using a
508 /// 32-bit signed integer to measure the time interval.
510 /// <param name="millisecondsTimeout">The number of milliseconds to wait, or <see
511 /// cref="Timeout.Infinite"/>(-1) to wait indefinitely.</param>
512 /// <returns>true if the <see cref="System.Threading.ManualResetEventSlim"/> was set; otherwise,
514 /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is a
515 /// negative number other than -1, which represents an infinite time-out.</exception>
516 /// <exception cref="T:System.InvalidOperationException">
517 /// The maximum number of waiters has been exceeded.
519 public bool Wait(int millisecondsTimeout
)
521 return Wait(millisecondsTimeout
, new CancellationToken());
525 /// Blocks the current thread until the current <see cref="ManualResetEventSlim"/> is set, using a
526 /// 32-bit signed integer to measure the time interval, while observing a <see
527 /// cref="T:System.Threading.CancellationToken"/>.
529 /// <param name="millisecondsTimeout">The number of milliseconds to wait, or <see
530 /// cref="Timeout.Infinite"/>(-1) to wait indefinitely.</param>
531 /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> to
533 /// <returns>true if the <see cref="System.Threading.ManualResetEventSlim"/> was set; otherwise,
535 /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is a
536 /// negative number other than -1, which represents an infinite time-out.</exception>
537 /// <exception cref="T:System.InvalidOperationException">
538 /// The maximum number of waiters has been exceeded.
540 /// <exception cref="T:System.Threading.OperationCanceledException"><paramref
541 /// name="cancellationToken"/> was canceled.</exception>
542 public bool Wait(int millisecondsTimeout
, CancellationToken cancellationToken
)
545 cancellationToken
.ThrowIfCancellationRequested(); // an early convenience check
547 if (millisecondsTimeout
< -1)
549 throw new ArgumentOutOfRangeException("millisecondsTimeout");
554 if (millisecondsTimeout
== 0)
556 // For 0-timeouts, we just return immediately.
561 // We spin briefly before falling back to allocating and/or waiting on a true event.
563 bool bNeedTimeoutAdjustment
= false;
564 int realMillisecondsTimeout
= millisecondsTimeout
; //this will be adjusted if necessary.
566 if (millisecondsTimeout
!= Timeout
.Infinite
)
568 // We will account for time spent spinning, so that we can decrement it from our
569 // timeout. In most cases the time spent in this section will be negligible. But
570 // we can't discount the possibility of our thread being switched out for a lengthy
571 // period of time. The timeout adjustments only take effect when and if we actually
572 // decide to block in the kernel below.
574 startTime
= TimeoutHelper
.GetTime();
575 bNeedTimeoutAdjustment
= true;
579 int HOW_MANY_SPIN_BEFORE_YIELD
= 10;
580 int HOW_MANY_YIELD_EVERY_SLEEP_0
= 5;
581 int HOW_MANY_YIELD_EVERY_SLEEP_1
= 20;
583 int spinCount
= SpinCount
;
584 for (int i
= 0; i
< spinCount
; i
++)
591 else if (i
< HOW_MANY_SPIN_BEFORE_YIELD
)
593 if (i
== HOW_MANY_SPIN_BEFORE_YIELD
/ 2)
603 Thread
.SpinWait(PlatformHelper
.ProcessorCount
* (4 << i
));
606 else if (i
% HOW_MANY_YIELD_EVERY_SLEEP_1
== 0)
610 else if (i
% HOW_MANY_YIELD_EVERY_SLEEP_0
== 0)
623 if (i
>= 100 && i
% 10 == 0) // check the cancellation token if the user passed a very large spin count
624 cancellationToken
.ThrowIfCancellationRequested();
627 // Now enter the lock and wait.
628 EnsureLockObjectCreated();
630 // We must register and deregister the token outside of the lock, to avoid deadlocks.
631 using (cancellationToken
.InternalRegisterWithoutEC(s_cancellationTokenCallback
, this))
635 // Loop to cope with spurious wakeups from other waits being canceled
638 // If our token was canceled, we must throw and exit.
639 cancellationToken
.ThrowIfCancellationRequested();
641 //update timeout (delays in wait commencement are due to spinning and/or spurious wakeups from other waits being canceled)
642 if (bNeedTimeoutAdjustment
)
644 realMillisecondsTimeout
= TimeoutHelper
.UpdateTimeOut(startTime
, millisecondsTimeout
);
645 if (realMillisecondsTimeout
<= 0)
649 // There is a ---- that Set will fail to see that there are waiters as Set does not take the lock,
650 // so after updating waiters, we must check IsSet again.
651 // Also, we must ensure there cannot be any reordering of the assignment to Waiters and the
652 // read from IsSet. This is guaranteed as Waiters{set;} involves an Interlocked.CompareExchange
653 // operation which provides a full memory barrier.
654 // If we see IsSet=false, then we are guaranteed that Set() will see that we are
655 // waiting and will pulse the monitor correctly.
657 Waiters
= Waiters
+ 1;
659 if (IsSet
) //This check must occur after updating Waiters.
661 Waiters
--; //revert the increment.
665 // Now finally perform the wait.
668 // ** the actual wait **
669 if (!Monitor
.Wait(m_lock
, realMillisecondsTimeout
))
670 return false; //return immediately if the timeout has expired.
674 // Clean up: we're done waiting.
675 Waiters
= Waiters
- 1;
678 // Now just loop back around, and the right thing will happen. Either:
679 // 1. We had a spurious wake-up due to some other wait being canceled via a different cancellationToken (rewait)
680 // or 2. the wait was successful. (the loop will break)
685 } // automatically disposes (and deregisters) the callback
687 return true; //done. The wait was satisfied.
691 /// Releases all resources used by the current instance of <see cref="ManualResetEventSlim"/>.
694 /// Unlike most of the members of <see cref="ManualResetEventSlim"/>, <see cref="Dispose()"/> is not
695 /// thread-safe and may not be used concurrently with other members of this instance.
697 public void Dispose()
700 GC
.SuppressFinalize(this);
704 /// When overridden in a derived class, releases the unmanaged resources used by the
705 /// <see cref="ManualResetEventSlim"/>, and optionally releases the managed resources.
707 /// <param name="disposing">true to release both managed and unmanaged resources;
708 /// false to release only unmanaged resources.</param>
710 /// Unlike most of the members of <see cref="ManualResetEventSlim"/>, <see cref="Dispose(Boolean)"/> is not
711 /// thread-safe and may not be used concurrently with other members of this instance.
713 protected virtual void Dispose(bool disposing
)
715 if ((m_combinedState
& Dispose_BitMask
) != 0)
716 return; // already disposed
718 m_combinedState
|= Dispose_BitMask
; //set the dispose bit
721 // We will dispose of the event object. We do this under a lock to protect
722 // against the race condition outlined in the Set method above.
723 ManualResetEvent eventObj
= m_eventObj
;
724 if (eventObj
!= null)
736 /// Throw ObjectDisposedException if the MRES is disposed
738 private void ThrowIfDisposed()
740 if ((m_combinedState
& Dispose_BitMask
) != 0)
741 throw new ObjectDisposedException(Environment
.GetResourceString("ManualResetEventSlim_Disposed"));
745 /// Private helper method to wake up waiters when a cancellationToken gets canceled.
747 private static Action
<object> s_cancellationTokenCallback
= new Action
<object>(CancellationTokenCallback
);
748 private static void CancellationTokenCallback(object obj
)
750 ManualResetEventSlim mre
= obj
as ManualResetEventSlim
;
751 Contract
.Assert(mre
!= null, "Expected a ManualResetEventSlim");
752 Contract
.Assert(mre
.m_lock
!= null); //the lock should have been created before this callback is registered for use.
755 Monitor
.PulseAll(mre
.m_lock
); // awaken all waiters
760 /// Private helper method for updating parts of a bit-string state value.
761 /// Mainly called from the IsSet and Waiters properties setters
764 /// Note: the parameter types must be int as CompareExchange cannot take a Uint
766 /// <param name="newBits">The new value</param>
767 /// <param name="updateBitsMask">The mask used to set the bits</param>
768 private void UpdateStateAtomically(int newBits
, int updateBitsMask
)
770 SpinWait sw
= new SpinWait();
772 Contract
.Assert((newBits
| updateBitsMask
) == updateBitsMask
, "newBits do not fall within the updateBitsMask.");
776 int oldState
= m_combinedState
; // cache the old value for testing in CAS
778 // Procedure:(1) zero the updateBits. eg oldState = [11111111] flag= [00111000] newState = [11000111]
779 // then (2) map in the newBits. eg [11000111] newBits=00101000, newState=[11101111]
780 int newState
= (oldState
& ~updateBitsMask
) | newBits
;
782 if (Interlocked
.CompareExchange(ref m_combinedState
, newState
, oldState
) == oldState
)
792 /// Private helper method - performs Mask and shift, particular helpful to extract a field from a packed word.
793 /// eg ExtractStatePortionAndShiftRight(0x12345678, 0xFF000000, 24) => 0x12, ie extracting the top 8-bits as a simple integer
795 /// ?? is there a common place to put this rather than being private to MRES?
797 /// <param name="state"></param>
798 /// <param name="mask"></param>
799 /// <param name="rightBitShiftCount"></param>
800 /// <returns></returns>
801 private static int ExtractStatePortionAndShiftRight(int state
, int mask
, int rightBitShiftCount
)
803 //convert to uint before shifting so that right-shift does not replicate the sign-bit,
804 //then convert back to int.
805 return unchecked((int)(((uint)(state
& mask
)) >> rightBitShiftCount
));
809 /// Performs a Mask operation, but does not perform the shift.
810 /// This is acceptable for boolean values for which the shift is unnecessary
811 /// eg (val & Mask) != 0 is an appropriate way to extract a boolean rather than using
812 /// ((val & Mask) >> shiftAmount) == 1
814 /// ?? is there a common place to put this rather than being private to MRES?
816 /// <param name="state"></param>
817 /// <param name="mask"></param>
818 private static int ExtractStatePortion(int state
, int mask
)