Updates referencesource to .NET 4.7
[mono-project.git] / mcs / class / referencesource / mscorlib / system / threading / ManualResetEventSlim.cs
blob2d1278ab9beb87cf138738a5f24cf162beb2b101
1 #pragma warning disable 0420
2 // ==++==
3 //
4 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //
6 // ==--==
7 // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
8 //
9 // SlimManualResetEvent.cs
11 // <OWNER>Microsoft</OWNER>
13 // An manual-reset event that mixes a little spinning with a true Win32 event.
15 // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
17 using System;
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.
34 // Notes:
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.
40 /// <summary>
41 /// Provides a slimmed down version of <see cref="T:System.Threading.ManualResetEvent"/>.
42 /// </summary>
43 /// <remarks>
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.
49 /// </remarks>
50 [ComVisible(false)]
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.
64 // -- State -- //
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
84 // ----------- //
86 #if DEBUG
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;
91 #endif
93 /// <summary>
94 /// Gets the underlying <see cref="T:System.Threading.WaitHandle"/> object for this <see
95 /// cref="ManualResetEventSlim"/>.
96 /// </summary>
97 /// <value>The underlying <see cref="T:System.Threading.WaitHandle"/> event object fore this <see
98 /// cref="ManualResetEventSlim"/>.</value>
99 /// <remarks>
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.
103 /// </remarks>
104 public WaitHandle WaitHandle
109 ThrowIfDisposed();
110 if (m_eventObj == null)
112 // Lazily initialize the event object if needed.
113 LazyInitializeEvent();
116 return m_eventObj;
120 /// <summary>
121 /// Gets whether the event is set.
122 /// </summary>
123 /// <value>true if the event has is set; otherwise, false.</value>
124 public bool IsSet
128 return 0 != ExtractStatePortion(m_combinedState, SignalledState_BitMask);
131 private set
133 UpdateStateAtomically(((value) ? 1 : 0) << SignalledState_ShiftCount, SignalledState_BitMask);
137 /// <summary>
138 /// Gets the number of spin waits that will be occur before falling back to a true wait.
139 /// </summary>
140 public int SpinCount
144 return ExtractStatePortionAndShiftRight(m_combinedState, SpinCountState_BitMask, SpinCountState_ShiftCount);
147 private set
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);
156 /// <summary>
157 /// How many threads are waiting.
158 /// </summary>
159 private int Waiters
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.
185 /// <summary>
186 /// Initializes a new instance of the <see cref="ManualResetEventSlim"/>
187 /// class with an initial state of nonsignaled.
188 /// </summary>
189 public ManualResetEventSlim()
190 : this(false)
195 /// <summary>
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.
198 /// </summary>
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);
208 /// <summary>
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
211 /// spin count.
212 /// </summary>
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
216 /// wait.</param>
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)
221 if (spinCount < 0)
223 throw new ArgumentOutOfRangeException("spinCount");
226 if (spinCount > SpinCountState_MaxValue)
228 throw new ArgumentOutOfRangeException(
229 "spinCount",
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);
237 /// <summary>
238 /// Initializes the internal state of the event.
239 /// </summary>
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;
254 /// <summary>
255 /// Helper to ensure the lock object is created before first use.
256 /// </summary>
257 private void EnsureLockObjectCreated()
259 Contract.Ensures(m_lock != null);
261 if (m_lock != null)
262 return;
264 object newObj = new object();
265 Interlocked.CompareExchange(ref m_lock, newObj, null); // failure is benign.. someone else won the ----.
268 /// <summary>
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.
273 /// </summary>
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.
285 newEventObj.Close();
287 return false;
289 else
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.
304 lock (newEventObj)
306 // If our event hasn't already been disposed of, we must set it.
307 if (m_eventObj == newEventObj)
309 newEventObj.Set();
314 return true;
318 /// <summary>
319 /// Sets the state of the event to signaled, which allows one or more threads waiting on the event to
320 /// proceed.
321 /// </summary>
322 public void Set()
324 Set(false);
327 /// <summary>
328 /// Private helper to actually perform the Set.
329 /// </summary>
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.
337 IsSet = true;
339 // If there are waiting threads, we need to pulse them.
340 if (Waiters > 0)
342 Contract.Assert(m_lock != null); //if waiters>0, then m_lock has already been created.
343 lock (m_lock)
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.
368 lock (eventObj)
370 if (m_eventObj != null)
372 // If somebody is waiting, we must set the event.
373 m_eventObj.Set();
378 #if DEBUG
379 m_lastSetTime = DateTime.Now.Ticks;
380 #endif
383 /// <summary>
384 /// Sets the state of the event to nonsignaled, which causes threads to block.
385 /// </summary>
386 /// <remarks>
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.
389 /// </remarks>
390 public void Reset()
392 ThrowIfDisposed();
393 // If there's an event, reset it.
394 if (m_eventObj != null)
396 m_eventObj.Reset();
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.
407 IsSet = false;
409 #if DEBUG
410 m_lastResetTime = DateTime.Now.Ticks;
411 #endif
414 /// <summary>
415 /// Blocks the current thread until the current <see cref="ManualResetEventSlim"/> is set.
416 /// </summary>
417 /// <exception cref="T:System.InvalidOperationException">
418 /// The maximum number of waiters has been exceeded.
419 /// </exception>
420 /// <remarks>
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.
423 /// </remarks>
424 public void Wait()
426 Wait(Timeout.Infinite, new CancellationToken());
429 /// <summary>
430 /// Blocks the current thread until the current <see cref="ManualResetEventSlim"/> receives a signal,
431 /// while observing a <see cref="T:System.Threading.CancellationToken"/>.
432 /// </summary>
433 /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> to
434 /// observe.</param>
435 /// <exception cref="T:System.InvalidOperationException">
436 /// The maximum number of waiters has been exceeded.
437 /// </exception>
438 /// <exception cref="T:System.OperationCanceledExcepton"><paramref name="cancellationToken"/> was
439 /// canceled.</exception>
440 /// <remarks>
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.
443 /// </remarks>
444 public void Wait(CancellationToken cancellationToken)
446 Wait(Timeout.Infinite, cancellationToken);
449 /// <summary>
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.
452 /// </summary>
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.
455 /// </param>
456 /// <returns>true if the <see cref="System.Threading.ManualResetEventSlim"/> was set; otherwise,
457 /// false.</returns>
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.
463 /// </exception>
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());
475 /// <summary>
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"/>.
479 /// </summary>
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.
482 /// </param>
483 /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> to
484 /// observe.</param>
485 /// <returns>true if the <see cref="System.Threading.ManualResetEventSlim"/> was set; otherwise,
486 /// false.</returns>
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.
494 /// </exception>
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);
506 /// <summary>
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.
509 /// </summary>
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,
513 /// false.</returns>
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.
518 /// </exception>
519 public bool Wait(int millisecondsTimeout)
521 return Wait(millisecondsTimeout, new CancellationToken());
524 /// <summary>
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"/>.
528 /// </summary>
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
532 /// observe.</param>
533 /// <returns>true if the <see cref="System.Threading.ManualResetEventSlim"/> was set; otherwise,
534 /// false.</returns>
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.
539 /// </exception>
540 /// <exception cref="T:System.Threading.OperationCanceledException"><paramref
541 /// name="cancellationToken"/> was canceled.</exception>
542 public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
544 ThrowIfDisposed();
545 cancellationToken.ThrowIfCancellationRequested(); // an early convenience check
547 if (millisecondsTimeout < -1)
549 throw new ArgumentOutOfRangeException("millisecondsTimeout");
552 if (!IsSet)
554 if (millisecondsTimeout == 0)
556 // For 0-timeouts, we just return immediately.
557 return false;
561 // We spin briefly before falling back to allocating and/or waiting on a true event.
562 uint startTime = 0;
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;
578 //spin
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++)
586 if (IsSet)
588 return true;
591 else if (i < HOW_MANY_SPIN_BEFORE_YIELD)
593 if (i == HOW_MANY_SPIN_BEFORE_YIELD / 2)
595 #if PFX_LEGACY_3_5
596 Platform.Yield();
597 #else
598 Thread.Yield();
599 #endif
601 else
603 Thread.SpinWait(PlatformHelper.ProcessorCount * (4 << i));
606 else if (i % HOW_MANY_YIELD_EVERY_SLEEP_1 == 0)
608 Thread.Sleep(1);
610 else if (i % HOW_MANY_YIELD_EVERY_SLEEP_0 == 0)
612 Thread.Sleep(0);
614 else
616 #if PFX_LEGACY_3_5
617 Platform.Yield();
618 #else
619 Thread.Yield();
620 #endif
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))
633 lock (m_lock)
635 // Loop to cope with spurious wakeups from other waits being canceled
636 while (!IsSet)
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)
646 return false;
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.
662 return true;
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.
672 finally
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.
690 /// <summary>
691 /// Releases all resources used by the current instance of <see cref="ManualResetEventSlim"/>.
692 /// </summary>
693 /// <remarks>
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.
696 /// </remarks>
697 public void Dispose()
699 Dispose(true);
700 GC.SuppressFinalize(this);
703 /// <summary>
704 /// When overridden in a derived class, releases the unmanaged resources used by the
705 /// <see cref="ManualResetEventSlim"/>, and optionally releases the managed resources.
706 /// </summary>
707 /// <param name="disposing">true to release both managed and unmanaged resources;
708 /// false to release only unmanaged resources.</param>
709 /// <remarks>
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.
712 /// </remarks>
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
719 if (disposing)
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)
726 lock (eventObj)
728 eventObj.Close();
729 m_eventObj = null;
735 /// <summary>
736 /// Throw ObjectDisposedException if the MRES is disposed
737 /// </summary>
738 private void ThrowIfDisposed()
740 if ((m_combinedState & Dispose_BitMask) != 0)
741 throw new ObjectDisposedException(Environment.GetResourceString("ManualResetEventSlim_Disposed"));
744 /// <summary>
745 /// Private helper method to wake up waiters when a cancellationToken gets canceled.
746 /// </summary>
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.
753 lock (mre.m_lock)
755 Monitor.PulseAll(mre.m_lock); // awaken all waiters
759 /// <summary>
760 /// Private helper method for updating parts of a bit-string state value.
761 /// Mainly called from the IsSet and Waiters properties setters
762 /// </summary>
763 /// <remarks>
764 /// Note: the parameter types must be int as CompareExchange cannot take a Uint
765 /// </remarks>
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)
784 return;
787 sw.SpinOnce();
788 } while (true);
791 /// <summary>
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
794 ///
795 /// ?? is there a common place to put this rather than being private to MRES?
796 /// </summary>
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));
808 /// <summary>
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 &amp; Mask) != 0 is an appropriate way to extract a boolean rather than using
812 /// ((val &amp; Mask) &gt;&gt; shiftAmount) == 1
813 ///
814 /// ?? is there a common place to put this rather than being private to MRES?
815 /// </summary>
816 /// <param name="state"></param>
817 /// <param name="mask"></param>
818 private static int ExtractStatePortion(int state, int mask)
820 return state & mask;