Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / mscorlib / system / threading / ManualResetEventSlim.cs
1 #pragma warning disable 0420
2 // ==++==
3 //
4 //   Copyright (c) Microsoft Corporation.  All rights reserved.
5 // 
6 // ==--==
7 // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
8 //
9 // SlimManualResetEvent.cs
10 //
11 // <OWNER>Microsoft</OWNER>
12 //
13 // An manual-reset event that mixes a little spinning with a true Win32 event.
14 //
15 // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
16
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;
23
24 namespace System.Threading
25 {
26
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.
33     //
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.
39
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
54     {
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;
58
59         private volatile object m_lock;
60         // A lock used for waiting and pulsing. Lazily initialized via EnsureLockObjectCreated()
61
62         private volatile ManualResetEvent m_eventObj; // A true Win32 event used for waiting.
63
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. 
67
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;
71
72         //1-bit for disposed state
73         private const int Dispose_BitMask = unchecked((int)0x40000000);//0100 0000 0000 0000 0000 0000 0000 0000
74
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
79
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         // ----------- //
85
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
92
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
105         {
106
107             get
108             {
109                 ThrowIfDisposed();
110                 if (m_eventObj == null)
111                 {
112                     // Lazily initialize the event object if needed.
113                     LazyInitializeEvent();
114                 }
115
116                 return m_eventObj;
117             }
118         }
119
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
125         {
126             get
127             {
128                 return 0 != ExtractStatePortion(m_combinedState, SignalledState_BitMask);
129             }
130
131             private set
132             {
133                 UpdateStateAtomically(((value) ? 1 : 0) << SignalledState_ShiftCount, SignalledState_BitMask);
134             }
135         }
136
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
141         {
142             get
143             {
144                 return ExtractStatePortionAndShiftRight(m_combinedState, SpinCountState_BitMask, SpinCountState_ShiftCount);
145             }
146
147             private set
148             {
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);
153             }
154         }
155
156         /// <summary>
157         /// How many threads are waiting.
158         /// </summary>
159         private int Waiters
160         {
161             get
162             {
163                 return ExtractStatePortionAndShiftRight(m_combinedState, NumWaitersState_BitMask, NumWaitersState_ShiftCount);
164             }
165
166             set
167             {
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.");
170
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));
174
175                 UpdateStateAtomically(value << NumWaitersState_ShiftCount, NumWaitersState_BitMask);
176             }
177
178         }
179
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.
183         //
184
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)
191         {
192
193         }
194
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)
202         {
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);
206         }
207
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)
220         {
221             if (spinCount < 0)
222             {
223                 throw new ArgumentOutOfRangeException("spinCount");
224             }
225
226             if (spinCount > SpinCountState_MaxValue)
227             {
228                 throw new ArgumentOutOfRangeException(
229                     "spinCount",
230                     String.Format(Environment.GetResourceString("ManualResetEventSlim_ctor_SpinCountOutOfRange"), SpinCountState_MaxValue));
231             }
232
233             // We will suppress default spin  because the user specified a count.
234             Initialize(initialState, spinCount);
235         }
236
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)
243         {
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.");
249
250             SpinCount = PlatformHelper.IsSingleProcessor ? DEFAULT_SPIN_SP : spinCount;
251
252         }
253
254         /// <summary>
255         /// Helper to ensure the lock object is created before first use.
256         /// </summary>
257         private void EnsureLockObjectCreated()
258         {
259             Contract.Ensures(m_lock != null);
260
261             if (m_lock != null)
262                 return;
263
264             object newObj = new object();
265             Interlocked.CompareExchange(ref m_lock, newObj, null); // failure is benign.. someone else won the ----.
266         }
267
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()
276         {
277             bool preInitializeIsSet = IsSet;
278             ManualResetEvent newEventObj = new ManualResetEvent(preInitializeIsSet);
279
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)
283             {
284                 // We raced with someone else and lost. Destroy the garbage event.
285                 newEventObj.Close();
286
287                 return false;
288             }
289             else
290             {
291
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)
299                 {
300                     Contract.Assert(currentIsSet,
301                         "The only safe concurrent transition is from unset->set: detected set->unset.");
302
303                     // We saw it as unsignaled, but it has since become set.
304                     lock (newEventObj)
305                     {
306                         // If our event hasn't already been disposed of, we must set it.
307                         if (m_eventObj == newEventObj)
308                         {
309                             newEventObj.Set();
310                         }
311                     }
312                 }
313
314                 return true;
315             }
316         }
317
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()
323         {
324             Set(false);
325         }
326
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)
333         {
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;
338
339             // If there are waiting threads, we need to pulse them.
340             if (Waiters > 0)
341             {
342                 Contract.Assert(m_lock != null); //if waiters>0, then m_lock has already been created.
343                 lock (m_lock)
344                 {
345
346                     Monitor.PulseAll(m_lock);
347                 }
348             }
349
350             ManualResetEvent eventObj = m_eventObj;
351
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
354
355             if (eventObj != null && !duringCancellation)
356             {
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.
367
368                 lock (eventObj)
369                 {
370                     if (m_eventObj != null)
371                     {
372                         // If somebody is waiting, we must set the event.
373                         m_eventObj.Set();
374                     }
375                 }
376             }
377
378 #if DEBUG
379             m_lastSetTime = DateTime.Now.Ticks;
380 #endif
381         }
382
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()
391         {
392             ThrowIfDisposed();
393             // If there's an event, reset it.
394             if (m_eventObj != null)
395             {
396                 m_eventObj.Reset();
397             }
398
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.
405
406             // And finally set our state back to unsignaled.
407             IsSet = false;
408
409 #if DEBUG
410             m_lastResetTime = DateTime.Now.Ticks;
411 #endif
412         }
413
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()
425         {
426             Wait(Timeout.Infinite, new CancellationToken());
427         }
428
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)
445         {
446             Wait(Timeout.Infinite, cancellationToken);
447         }
448
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)
465         {
466             long totalMilliseconds = (long)timeout.TotalMilliseconds;
467             if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue)
468             {
469                 throw new ArgumentOutOfRangeException("timeout");
470             }
471
472             return Wait((int)totalMilliseconds, new CancellationToken());
473         }
474
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)
496         {
497             long totalMilliseconds = (long)timeout.TotalMilliseconds;
498             if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue)
499             {
500                 throw new ArgumentOutOfRangeException("timeout");
501             }
502
503             return Wait((int)totalMilliseconds, cancellationToken);
504         }
505
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)
520         {
521             return Wait(millisecondsTimeout, new CancellationToken());
522         }
523
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)
543         {
544             ThrowIfDisposed();
545             cancellationToken.ThrowIfCancellationRequested(); // an early convenience check
546
547             if (millisecondsTimeout < -1)
548             {
549                 throw new ArgumentOutOfRangeException("millisecondsTimeout");
550             }
551
552             if (!IsSet)
553             {
554                 if (millisecondsTimeout == 0)
555                 {
556                     // For 0-timeouts, we just return immediately.
557                     return false;
558                 }
559
560
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.
565
566                 if (millisecondsTimeout != Timeout.Infinite)
567                 {
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.
573
574                     startTime = TimeoutHelper.GetTime();
575                     bNeedTimeoutAdjustment = true;
576                 }
577
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;
582
583                 int spinCount = SpinCount;
584                 for (int i = 0; i < spinCount; i++)
585                 {
586                     if (IsSet)
587                     {
588                         return true;
589                     }
590
591                     else if (i < HOW_MANY_SPIN_BEFORE_YIELD)
592                     {
593                         if (i == HOW_MANY_SPIN_BEFORE_YIELD / 2)
594                         {
595 #if PFX_LEGACY_3_5
596                             Platform.Yield();
597 #else
598                             Thread.Yield();
599 #endif
600                         }
601                         else
602                         {
603                             Thread.SpinWait(PlatformHelper.ProcessorCount * (4 << i));
604                         }
605                     }
606                     else if (i % HOW_MANY_YIELD_EVERY_SLEEP_1 == 0)
607                     {
608                         Thread.Sleep(1);
609                     }
610                     else if (i % HOW_MANY_YIELD_EVERY_SLEEP_0 == 0)
611                     {
612                         Thread.Sleep(0);
613                     }
614                     else
615                     {
616 #if PFX_LEGACY_3_5
617                         Platform.Yield();
618 #else
619                         Thread.Yield();
620 #endif
621                     }
622
623                     if (i >= 100 && i % 10 == 0) // check the cancellation token if the user passed a very large spin count
624                         cancellationToken.ThrowIfCancellationRequested();
625                 }
626
627                 // Now enter the lock and wait.
628                 EnsureLockObjectCreated();
629
630                 // We must register and deregister the token outside of the lock, to avoid deadlocks.
631                 using (cancellationToken.InternalRegisterWithoutEC(s_cancellationTokenCallback, this))
632                 {
633                     lock (m_lock)
634                     {
635                         // Loop to cope with spurious wakeups from other waits being canceled
636                         while (!IsSet)
637                         {
638                             // If our token was canceled, we must throw and exit.
639                             cancellationToken.ThrowIfCancellationRequested();
640
641                             //update timeout (delays in wait commencement are due to spinning and/or spurious wakeups from other waits being canceled)
642                             if (bNeedTimeoutAdjustment)
643                             {
644                                 realMillisecondsTimeout = TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout);
645                                 if (realMillisecondsTimeout <= 0)
646                                     return false;
647                             }
648
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.
656
657                             Waiters = Waiters + 1;
658
659                             if (IsSet) //This check must occur after updating Waiters.
660                             {
661                                 Waiters--; //revert the increment.
662                                 return true;
663                             }
664
665                             // Now finally perform the wait.
666                             try
667                             {
668                                 // ** the actual wait **
669                                 if (!Monitor.Wait(m_lock, realMillisecondsTimeout))
670                                     return false; //return immediately if the timeout has expired.
671                             }
672                             finally
673                             {
674                                 // Clean up: we're done waiting.
675                                 Waiters = Waiters - 1;
676                             }
677
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)
681
682                         }
683                     }
684                 }
685             } // automatically disposes (and deregisters) the callback 
686
687             return true; //done. The wait was satisfied.
688         }
689
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()
698         {
699             Dispose(true);
700             GC.SuppressFinalize(this);
701         }
702
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)
714         {
715             if ((m_combinedState & Dispose_BitMask) != 0)
716                 return; // already disposed
717
718             m_combinedState |= Dispose_BitMask; //set the dispose bit
719             if (disposing)
720             {
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)
725                 {
726                     lock (eventObj)
727                     {
728                         eventObj.Close();
729                         m_eventObj = null;
730                     }
731                 }
732             }
733         }
734
735         /// <summary>
736         /// Throw ObjectDisposedException if the MRES is disposed
737         /// </summary>
738         private void ThrowIfDisposed()
739         {
740             if ((m_combinedState & Dispose_BitMask) != 0)
741                 throw new ObjectDisposedException(Environment.GetResourceString("ManualResetEventSlim_Disposed"));
742         }
743
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)
749         {
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)
754             {
755                 Monitor.PulseAll(mre.m_lock); // awaken all waiters
756             }
757         }
758
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)
769         {
770             SpinWait sw = new SpinWait();
771
772             Contract.Assert((newBits | updateBitsMask) == updateBitsMask, "newBits do not fall within the updateBitsMask.");
773
774             do
775             {
776                 int oldState = m_combinedState; // cache the old value for testing in CAS
777
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;
781
782                 if (Interlocked.CompareExchange(ref m_combinedState, newState, oldState) == oldState)
783                 {
784                     return;
785                 }
786
787                 sw.SpinOnce();
788             } while (true);
789         }
790
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)
802         {
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));
806         }
807
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)
819         {
820             return state & mask;
821         }
822     }
823 }