More corelib cleanup (dotnet/coreclr#26993)
[mono-project.git] / netcore / System.Private.CoreLib / shared / System / Threading / ManualResetEventSlim.cs
blobe804d382450fd12ede21950e515a35010919eea5
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
5 using System.Diagnostics;
7 namespace System.Threading
9 // ManualResetEventSlim wraps a manual-reset event internally with a little bit of
10 // spinning. When an event will be set imminently, it is often advantageous to avoid
11 // a 4k+ cycle context switch in favor of briefly spinning. Therefore we layer on to
12 // a brief amount of spinning that should, on the average, make using the slim event
13 // cheaper than using Win32 events directly. This can be reset manually, much like
14 // a Win32 manual-reset would be.
16 // Notes:
17 // We lazily allocate the Win32 event internally. Therefore, the caller should
18 // always call Dispose to clean it up, just in case. This API is a no-op of the
19 // event wasn't allocated, but if it was, ensures that the event goes away
20 // eagerly, instead of waiting for finalization.
22 /// <summary>
23 /// Provides a slimmed down version of <see cref="System.Threading.ManualResetEvent"/>.
24 /// </summary>
25 /// <remarks>
26 /// All public and protected members of <see cref="ManualResetEventSlim"/> are thread-safe and may be used
27 /// concurrently from multiple threads, with the exception of Dispose, which
28 /// must only be used when all other operations on the <see cref="ManualResetEventSlim"/> have
29 /// completed, and Reset, which should only be used when no other threads are
30 /// accessing the event.
31 /// </remarks>
32 [DebuggerDisplay("Set = {IsSet}")]
33 public class ManualResetEventSlim : IDisposable
35 // These are the default spin counts we use on single-proc and MP machines.
36 private const int DEFAULT_SPIN_SP = 1;
38 private volatile object? m_lock;
39 // A lock used for waiting and pulsing. Lazily initialized via EnsureLockObjectCreated()
41 private volatile ManualResetEvent? m_eventObj; // A true Win32 event used for waiting.
43 // -- State -- //
44 // For a packed word a uint would seem better, but Interlocked.* doesn't support them as uint isn't CLS-compliant.
45 private volatile int m_combinedState; // ie a uint. Used for the state items listed below.
47 // 1-bit for signalled state
48 private const int SignalledState_BitMask = unchecked((int)0x80000000); // 1000 0000 0000 0000 0000 0000 0000 0000
49 private const int SignalledState_ShiftCount = 31;
51 // 1-bit for disposed state
52 private const int Dispose_BitMask = unchecked((int)0x40000000); // 0100 0000 0000 0000 0000 0000 0000 0000
54 // 11-bits for m_spinCount
55 private const int SpinCountState_BitMask = unchecked((int)0x3FF80000); // 0011 1111 1111 1000 0000 0000 0000 0000
56 private const int SpinCountState_ShiftCount = 19;
57 private const int SpinCountState_MaxValue = (1 << 11) - 1; // 2047
59 // 19-bits for m_waiters. This allows support of 512K threads waiting which should be ample
60 private const int NumWaitersState_BitMask = unchecked((int)0x0007FFFF); // 0000 0000 0000 0111 1111 1111 1111 1111
61 private const int NumWaitersState_ShiftCount = 0;
62 private const int NumWaitersState_MaxValue = (1 << 19) - 1; // 512K-1
63 // ----------- //
65 /// <summary>
66 /// Gets the underlying <see cref="System.Threading.WaitHandle"/> object for this <see
67 /// cref="ManualResetEventSlim"/>.
68 /// </summary>
69 /// <value>The underlying <see cref="System.Threading.WaitHandle"/> event object fore this <see
70 /// cref="ManualResetEventSlim"/>.</value>
71 /// <remarks>
72 /// Accessing this property forces initialization of an underlying event object if one hasn't
73 /// already been created. To simply wait on this <see cref="ManualResetEventSlim"/>,
74 /// the public Wait methods should be preferred.
75 /// </remarks>
76 public WaitHandle WaitHandle
78 get
80 ThrowIfDisposed();
81 if (m_eventObj == null)
83 // Lazily initialize the event object if needed.
84 LazyInitializeEvent();
85 Debug.Assert(m_eventObj != null);
88 return m_eventObj;
92 /// <summary>
93 /// Gets whether the event is set.
94 /// </summary>
95 /// <value>true if the event has is set; otherwise, false.</value>
96 public bool IsSet
98 get => 0 != ExtractStatePortion(m_combinedState, SignalledState_BitMask);
99 private set => UpdateStateAtomically(((value) ? 1 : 0) << SignalledState_ShiftCount, SignalledState_BitMask);
102 /// <summary>
103 /// Gets the number of spin waits that will be occur before falling back to a true wait.
104 /// </summary>
105 public int SpinCount
107 get => ExtractStatePortionAndShiftRight(m_combinedState, SpinCountState_BitMask, SpinCountState_ShiftCount);
108 private set
110 Debug.Assert(value >= 0, "SpinCount is a restricted-width integer. The value supplied is outside the legal range.");
111 Debug.Assert(value <= SpinCountState_MaxValue, "SpinCount is a restricted-width integer. The value supplied is outside the legal range.");
112 // Don't worry about thread safety because it's set one time from the constructor
113 m_combinedState = (m_combinedState & ~SpinCountState_BitMask) | (value << SpinCountState_ShiftCount);
117 /// <summary>
118 /// How many threads are waiting.
119 /// </summary>
120 private int Waiters
122 get => ExtractStatePortionAndShiftRight(m_combinedState, NumWaitersState_BitMask, NumWaitersState_ShiftCount);
125 // setting to <0 would indicate an internal flaw, hence Assert is appropriate.
126 Debug.Assert(value >= 0, "NumWaiters should never be less than zero. This indicates an internal error.");
128 // it is possible for the max number of waiters to be exceeded via user-code, hence we use a real exception here.
129 if (value >= NumWaitersState_MaxValue)
130 throw new InvalidOperationException(SR.Format(SR.ManualResetEventSlim_ctor_TooManyWaiters, NumWaitersState_MaxValue));
132 UpdateStateAtomically(value << NumWaitersState_ShiftCount, NumWaitersState_BitMask);
136 //-----------------------------------------------------------------------------------
137 // Constructs a new event, optionally specifying the initial state and spin count.
138 // The defaults are that the event is unsignaled and some reasonable default spin.
141 /// <summary>
142 /// Initializes a new instance of the <see cref="ManualResetEventSlim"/>
143 /// class with an initial state of nonsignaled.
144 /// </summary>
145 public ManualResetEventSlim()
146 : this(false)
150 /// <summary>
151 /// Initializes a new instance of the <see cref="ManualResetEventSlim"/>
152 /// class with a boolean value indicating whether to set the initial state to signaled.
153 /// </summary>
154 /// <param name="initialState">true to set the initial state signaled; false to set the initial state
155 /// to nonsignaled.</param>
156 public ManualResetEventSlim(bool initialState)
158 // Specify the default spin count, and use default spin if we're
159 // on a multi-processor machine. Otherwise, we won't.
160 Initialize(initialState, SpinWait.SpinCountforSpinBeforeWait);
163 /// <summary>
164 /// Initializes a new instance of the <see cref="ManualResetEventSlim"/>
165 /// class with a Boolean value indicating whether to set the initial state to signaled and a specified
166 /// spin count.
167 /// </summary>
168 /// <param name="initialState">true to set the initial state to signaled; false to set the initial state
169 /// to nonsignaled.</param>
170 /// <param name="spinCount">The number of spin waits that will occur before falling back to a true
171 /// wait.</param>
172 /// <exception cref="System.ArgumentOutOfRangeException"><paramref name="spinCount"/> is less than
173 /// 0 or greater than the maximum allowed value.</exception>
174 public ManualResetEventSlim(bool initialState, int spinCount)
176 if (spinCount < 0)
178 throw new ArgumentOutOfRangeException(nameof(spinCount));
181 if (spinCount > SpinCountState_MaxValue)
183 throw new ArgumentOutOfRangeException(
184 nameof(spinCount),
185 SR.Format(SR.ManualResetEventSlim_ctor_SpinCountOutOfRange, SpinCountState_MaxValue));
188 // We will suppress default spin because the user specified a count.
189 Initialize(initialState, spinCount);
192 /// <summary>
193 /// Initializes the internal state of the event.
194 /// </summary>
195 /// <param name="initialState">Whether the event is set initially or not.</param>
196 /// <param name="spinCount">The spin count that decides when the event will block.</param>
197 private void Initialize(bool initialState, int spinCount)
199 m_combinedState = initialState ? (1 << SignalledState_ShiftCount) : 0;
200 // the spinCount argument has been validated by the ctors.
201 // but we now sanity check our predefined constants.
202 Debug.Assert(DEFAULT_SPIN_SP >= 0, "Internal error - DEFAULT_SPIN_SP is outside the legal range.");
203 Debug.Assert(DEFAULT_SPIN_SP <= SpinCountState_MaxValue, "Internal error - DEFAULT_SPIN_SP is outside the legal range.");
205 SpinCount = PlatformHelper.IsSingleProcessor ? DEFAULT_SPIN_SP : spinCount;
208 /// <summary>
209 /// Helper to ensure the lock object is created before first use.
210 /// </summary>
211 private void EnsureLockObjectCreated()
213 if (m_lock != null)
214 return;
216 object newObj = new object();
217 Interlocked.CompareExchange(ref m_lock, newObj, null); // failure is benign. Someone else set the value.
220 /// <summary>
221 /// This method lazily initializes the event object. It uses CAS to guarantee that
222 /// many threads racing to call this at once don't result in more than one event
223 /// being stored and used. The event will be signaled or unsignaled depending on
224 /// the state of the thin-event itself, with synchronization taken into account.
225 /// </summary>
226 private void LazyInitializeEvent()
228 bool preInitializeIsSet = IsSet;
229 ManualResetEvent newEventObj = new ManualResetEvent(preInitializeIsSet);
231 // We have to CAS this in case we are racing with another thread. We must
232 // guarantee only one event is actually stored in this field.
233 if (Interlocked.CompareExchange(ref m_eventObj, newEventObj, null) != null)
235 // Someone else set the value due to a race condition. Destroy the garbage event.
236 newEventObj.Dispose();
238 else
240 // Now that the event is published, verify that the state hasn't changed since
241 // we snapped the preInitializeState. Another thread could have done that
242 // between our initial observation above and here. The barrier incurred from
243 // the CAS above (in addition to m_state being volatile) prevents this read
244 // from moving earlier and being collapsed with our original one.
245 bool currentIsSet = IsSet;
246 if (currentIsSet != preInitializeIsSet)
248 Debug.Assert(currentIsSet,
249 "The only safe concurrent transition is from unset->set: detected set->unset.");
251 // We saw it as unsignaled, but it has since become set.
252 lock (newEventObj)
254 // If our event hasn't already been disposed of, we must set it.
255 if (m_eventObj == newEventObj)
257 newEventObj.Set();
264 /// <summary>
265 /// Sets the state of the event to signaled, which allows one or more threads waiting on the event to
266 /// proceed.
267 /// </summary>
268 public void Set()
270 Set(false);
273 /// <summary>
274 /// Private helper to actually perform the Set.
275 /// </summary>
276 /// <param name="duringCancellation">Indicates whether we are calling Set() during cancellation.</param>
277 /// <exception cref="System.OperationCanceledException">The object has been canceled.</exception>
278 private void Set(bool duringCancellation)
280 // We need to ensure that IsSet=true does not get reordered past the read of m_eventObj
281 // This would be a legal movement according to the .NET memory model.
282 // The code is safe as IsSet involves an Interlocked.CompareExchange which provides a full memory barrier.
283 IsSet = true;
285 // If there are waiting threads, we need to pulse them.
286 if (Waiters > 0)
288 Debug.Assert(m_lock != null); // if waiters>0, then m_lock has already been created.
289 lock (m_lock)
291 Monitor.PulseAll(m_lock);
295 ManualResetEvent? eventObj = m_eventObj;
297 // Design-decision: do not set the event if we are in cancellation -> better to deadlock than to wake up waiters incorrectly
298 // It would be preferable to wake up the event and have it throw OCE. This requires MRE to implement cancellation logic
300 if (eventObj != null && !duringCancellation)
302 // We must surround this call to Set in a lock. The reason is fairly subtle.
303 // Sometimes a thread will issue a Wait and wake up after we have set m_state,
304 // but before we have gotten around to setting m_eventObj (just below). That's
305 // because Wait first checks m_state and will only access the event if absolutely
306 // necessary. However, the coding pattern { event.Wait(); event.Dispose() } is
307 // quite common, and we must support it. If the waiter woke up and disposed of
308 // the event object before the setter has finished, however, we would try to set a
309 // now-disposed Win32 event. Crash! To deal with this race condition, we use a lock to
310 // protect access to the event object when setting and disposing of it. We also
311 // double-check that the event has not become null in the meantime when in the lock.
313 lock (eventObj)
315 if (m_eventObj != null)
317 // If somebody is waiting, we must set the event.
318 m_eventObj.Set();
324 /// <summary>
325 /// Sets the state of the event to nonsignaled, which causes threads to block.
326 /// </summary>
327 /// <remarks>
328 /// Unlike most of the members of <see cref="ManualResetEventSlim"/>, <see cref="Reset()"/> is not
329 /// thread-safe and may not be used concurrently with other members of this instance.
330 /// </remarks>
331 public void Reset()
333 ThrowIfDisposed();
334 // If there's an event, reset it.
335 if (m_eventObj != null)
337 m_eventObj.Reset();
340 // There is a race condition here. If another thread Sets the event, we will get into a state
341 // where m_state will be unsignaled, yet the Win32 event object will have been signaled.
342 // This could cause waiting threads to wake up even though the event is in an
343 // unsignaled state. This is fine -- those that are calling Reset concurrently are
344 // responsible for doing "the right thing" -- e.g. rechecking the condition and
345 // resetting the event manually.
347 // And finally set our state back to unsignaled.
348 IsSet = false;
351 /// <summary>
352 /// Blocks the current thread until the current <see cref="ManualResetEventSlim"/> is set.
353 /// </summary>
354 /// <exception cref="System.InvalidOperationException">
355 /// The maximum number of waiters has been exceeded.
356 /// </exception>
357 /// <remarks>
358 /// The caller of this method blocks indefinitely until the current instance is set. The caller will
359 /// return immediately if the event is currently in a set state.
360 /// </remarks>
361 public void Wait()
363 Wait(Timeout.Infinite, new CancellationToken());
366 /// <summary>
367 /// Blocks the current thread until the current <see cref="ManualResetEventSlim"/> receives a signal,
368 /// while observing a <see cref="System.Threading.CancellationToken"/>.
369 /// </summary>
370 /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken"/> to
371 /// observe.</param>
372 /// <exception cref="System.InvalidOperationException">
373 /// The maximum number of waiters has been exceeded.
374 /// </exception>
375 /// <exception cref="System.OperationCanceledException"><paramref name="cancellationToken"/> was
376 /// canceled.</exception>
377 /// <remarks>
378 /// The caller of this method blocks indefinitely until the current instance is set. The caller will
379 /// return immediately if the event is currently in a set state.
380 /// </remarks>
381 public void Wait(CancellationToken cancellationToken)
383 Wait(Timeout.Infinite, cancellationToken);
386 /// <summary>
387 /// Blocks the current thread until the current <see cref="ManualResetEventSlim"/> is set, using a
388 /// <see cref="System.TimeSpan"/> to measure the time interval.
389 /// </summary>
390 /// <param name="timeout">A <see cref="System.TimeSpan"/> that represents the number of milliseconds
391 /// to wait, or a <see cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely.
392 /// </param>
393 /// <returns>true if the <see cref="System.Threading.ManualResetEventSlim"/> was set; otherwise,
394 /// false.</returns>
395 /// <exception cref="System.ArgumentOutOfRangeException"><paramref name="timeout"/> is a negative
396 /// number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater
397 /// than <see cref="int.MaxValue"/>.</exception>
398 /// <exception cref="System.InvalidOperationException">
399 /// The maximum number of waiters has been exceeded.
400 /// </exception>
401 public bool Wait(TimeSpan timeout)
403 long totalMilliseconds = (long)timeout.TotalMilliseconds;
404 if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue)
406 throw new ArgumentOutOfRangeException(nameof(timeout));
409 return Wait((int)totalMilliseconds, new CancellationToken());
412 /// <summary>
413 /// Blocks the current thread until the current <see cref="ManualResetEventSlim"/> is set, using a
414 /// <see cref="System.TimeSpan"/> to measure the time interval, while observing a <see
415 /// cref="System.Threading.CancellationToken"/>.
416 /// </summary>
417 /// <param name="timeout">A <see cref="System.TimeSpan"/> that represents the number of milliseconds
418 /// to wait, or a <see cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely.
419 /// </param>
420 /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken"/> to
421 /// observe.</param>
422 /// <returns>true if the <see cref="System.Threading.ManualResetEventSlim"/> was set; otherwise,
423 /// false.</returns>
424 /// <exception cref="System.ArgumentOutOfRangeException"><paramref name="timeout"/> is a negative
425 /// number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater
426 /// than <see cref="int.MaxValue"/>.</exception>
427 /// <exception cref="System.OperationCanceledException"><paramref
428 /// name="cancellationToken"/> was canceled.</exception>
429 /// <exception cref="System.InvalidOperationException">
430 /// The maximum number of waiters has been exceeded.
431 /// </exception>
432 public bool Wait(TimeSpan timeout, CancellationToken cancellationToken)
434 long totalMilliseconds = (long)timeout.TotalMilliseconds;
435 if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue)
437 throw new ArgumentOutOfRangeException(nameof(timeout));
440 return Wait((int)totalMilliseconds, cancellationToken);
443 /// <summary>
444 /// Blocks the current thread until the current <see cref="ManualResetEventSlim"/> is set, using a
445 /// 32-bit signed integer to measure the time interval.
446 /// </summary>
447 /// <param name="millisecondsTimeout">The number of milliseconds to wait, or <see
448 /// cref="Timeout.Infinite"/>(-1) to wait indefinitely.</param>
449 /// <returns>true if the <see cref="System.Threading.ManualResetEventSlim"/> was set; otherwise,
450 /// false.</returns>
451 /// <exception cref="System.ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is a
452 /// negative number other than -1, which represents an infinite time-out.</exception>
453 /// <exception cref="System.InvalidOperationException">
454 /// The maximum number of waiters has been exceeded.
455 /// </exception>
456 public bool Wait(int millisecondsTimeout)
458 return Wait(millisecondsTimeout, new CancellationToken());
461 /// <summary>
462 /// Blocks the current thread until the current <see cref="ManualResetEventSlim"/> is set, using a
463 /// 32-bit signed integer to measure the time interval, while observing a <see
464 /// cref="System.Threading.CancellationToken"/>.
465 /// </summary>
466 /// <param name="millisecondsTimeout">The number of milliseconds to wait, or <see
467 /// cref="Timeout.Infinite"/>(-1) to wait indefinitely.</param>
468 /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken"/> to
469 /// observe.</param>
470 /// <returns>true if the <see cref="System.Threading.ManualResetEventSlim"/> was set; otherwise,
471 /// false.</returns>
472 /// <exception cref="System.ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is a
473 /// negative number other than -1, which represents an infinite time-out.</exception>
474 /// <exception cref="System.InvalidOperationException">
475 /// The maximum number of waiters has been exceeded.
476 /// </exception>
477 /// <exception cref="System.OperationCanceledException"><paramref
478 /// name="cancellationToken"/> was canceled.</exception>
479 public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
481 ThrowIfDisposed();
482 cancellationToken.ThrowIfCancellationRequested(); // an early convenience check
484 if (millisecondsTimeout < -1)
486 throw new ArgumentOutOfRangeException(nameof(millisecondsTimeout));
489 if (!IsSet)
491 if (millisecondsTimeout == 0)
493 // For 0-timeouts, we just return immediately.
494 return false;
498 // We spin briefly before falling back to allocating and/or waiting on a true event.
499 uint startTime = 0;
500 bool bNeedTimeoutAdjustment = false;
501 int realMillisecondsTimeout = millisecondsTimeout; // this will be adjusted if necessary.
503 if (millisecondsTimeout != Timeout.Infinite)
505 // We will account for time spent spinning, so that we can decrement it from our
506 // timeout. In most cases the time spent in this section will be negligible. But
507 // we can't discount the possibility of our thread being switched out for a lengthy
508 // period of time. The timeout adjustments only take effect when and if we actually
509 // decide to block in the kernel below.
511 startTime = TimeoutHelper.GetTime();
512 bNeedTimeoutAdjustment = true;
515 // Spin
516 int spinCount = SpinCount;
517 var spinner = new SpinWait();
518 while (spinner.Count < spinCount)
520 spinner.SpinOnce(sleep1Threshold: -1);
522 if (IsSet)
524 return true;
527 if (spinner.Count >= 100 && spinner.Count % 10 == 0) // check the cancellation token if the user passed a very large spin count
528 cancellationToken.ThrowIfCancellationRequested();
531 // Now enter the lock and wait. Must be created before registering the cancellation callback,
532 // which will try to take this lock.
533 EnsureLockObjectCreated();
535 // We must register and unregister the token outside of the lock, to avoid deadlocks.
536 using (cancellationToken.UnsafeRegister(s_cancellationTokenCallback, this))
538 lock (m_lock!)
540 // Loop to cope with spurious wakeups from other waits being canceled
541 while (!IsSet)
543 // If our token was canceled, we must throw and exit.
544 cancellationToken.ThrowIfCancellationRequested();
546 // update timeout (delays in wait commencement are due to spinning and/or spurious wakeups from other waits being canceled)
547 if (bNeedTimeoutAdjustment)
549 realMillisecondsTimeout = TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout);
550 if (realMillisecondsTimeout <= 0)
551 return false;
554 // There is a race condition that Set will fail to see that there are waiters as Set does not take the lock,
555 // so after updating waiters, we must check IsSet again.
556 // Also, we must ensure there cannot be any reordering of the assignment to Waiters and the
557 // read from IsSet. This is guaranteed as Waiters{set;} involves an Interlocked.CompareExchange
558 // operation which provides a full memory barrier.
559 // If we see IsSet=false, then we are guaranteed that Set() will see that we are
560 // waiting and will pulse the monitor correctly.
562 Waiters++;
564 if (IsSet) // This check must occur after updating Waiters.
566 Waiters--; // revert the increment.
567 return true;
570 // Now finally perform the wait.
573 // ** the actual wait **
574 if (!Monitor.Wait(m_lock, realMillisecondsTimeout))
575 return false; // return immediately if the timeout has expired.
577 finally
579 // Clean up: we're done waiting.
580 Waiters--;
582 // Now just loop back around, and the right thing will happen. Either:
583 // 1. We had a spurious wake-up due to some other wait being canceled via a different cancellationToken (rewait)
584 // or 2. the wait was successful. (the loop will break)
588 } // automatically disposes (and unregisters) the callback
590 return true; // done. The wait was satisfied.
593 /// <summary>
594 /// Releases all resources used by the current instance of <see cref="ManualResetEventSlim"/>.
595 /// </summary>
596 /// <remarks>
597 /// Unlike most of the members of <see cref="ManualResetEventSlim"/>, <see cref="Dispose()"/> is not
598 /// thread-safe and may not be used concurrently with other members of this instance.
599 /// </remarks>
600 public void Dispose()
602 Dispose(true);
603 GC.SuppressFinalize(this);
606 /// <summary>
607 /// When overridden in a derived class, releases the unmanaged resources used by the
608 /// <see cref="ManualResetEventSlim"/>, and optionally releases the managed resources.
609 /// </summary>
610 /// <param name="disposing">true to release both managed and unmanaged resources;
611 /// false to release only unmanaged resources.</param>
612 /// <remarks>
613 /// Unlike most of the members of <see cref="ManualResetEventSlim"/>, <see cref="Dispose(bool)"/> is not
614 /// thread-safe and may not be used concurrently with other members of this instance.
615 /// </remarks>
616 protected virtual void Dispose(bool disposing)
618 if ((m_combinedState & Dispose_BitMask) != 0)
619 return; // already disposed
621 m_combinedState |= Dispose_BitMask; // set the dispose bit
622 if (disposing)
624 // We will dispose of the event object. We do this under a lock to protect
625 // against the race condition outlined in the Set method above.
626 ManualResetEvent? eventObj = m_eventObj;
627 if (eventObj != null)
629 lock (eventObj)
631 eventObj.Dispose();
632 m_eventObj = null;
638 /// <summary>
639 /// Throw ObjectDisposedException if the MRES is disposed
640 /// </summary>
641 private void ThrowIfDisposed()
643 if ((m_combinedState & Dispose_BitMask) != 0)
644 throw new ObjectDisposedException(SR.ManualResetEventSlim_Disposed);
647 /// <summary>
648 /// Private helper method to wake up waiters when a cancellationToken gets canceled.
649 /// </summary>
650 private static readonly Action<object?> s_cancellationTokenCallback = new Action<object?>(CancellationTokenCallback);
651 private static void CancellationTokenCallback(object? obj)
653 Debug.Assert(obj is ManualResetEventSlim, "Expected a ManualResetEventSlim");
654 ManualResetEventSlim mre = (ManualResetEventSlim)obj;
655 Debug.Assert(mre.m_lock != null); // the lock should have been created before this callback is registered for use.
656 lock (mre.m_lock)
658 Monitor.PulseAll(mre.m_lock); // awaken all waiters
662 /// <summary>
663 /// Private helper method for updating parts of a bit-string state value.
664 /// Mainly called from the IsSet and Waiters properties setters
665 /// </summary>
666 /// <remarks>
667 /// Note: the parameter types must be int as CompareExchange cannot take a Uint
668 /// </remarks>
669 /// <param name="newBits">The new value</param>
670 /// <param name="updateBitsMask">The mask used to set the bits</param>
671 private void UpdateStateAtomically(int newBits, int updateBitsMask)
673 SpinWait sw = new SpinWait();
675 Debug.Assert((newBits | updateBitsMask) == updateBitsMask, "newBits do not fall within the updateBitsMask.");
677 while (true)
679 int oldState = m_combinedState; // cache the old value for testing in CAS
681 // Procedure:(1) zero the updateBits. eg oldState = [11111111] flag= [00111000] newState = [11000111]
682 // then (2) map in the newBits. eg [11000111] newBits=00101000, newState=[11101111]
683 int newState = (oldState & ~updateBitsMask) | newBits;
685 if (Interlocked.CompareExchange(ref m_combinedState, newState, oldState) == oldState)
687 return;
690 sw.SpinOnce(sleep1Threshold: -1);
694 /// <summary>
695 /// Private helper method - performs Mask and shift, particular helpful to extract a field from a packed word.
696 /// eg ExtractStatePortionAndShiftRight(0x12345678, 0xFF000000, 24) => 0x12, ie extracting the top 8-bits as a simple integer
698 /// ?? is there a common place to put this rather than being private to MRES?
699 /// </summary>
700 /// <param name="state"></param>
701 /// <param name="mask"></param>
702 /// <param name="rightBitShiftCount"></param>
703 /// <returns></returns>
704 private static int ExtractStatePortionAndShiftRight(int state, int mask, int rightBitShiftCount)
706 // convert to uint before shifting so that right-shift does not replicate the sign-bit,
707 // then convert back to int.
708 return unchecked((int)(((uint)(state & mask)) >> rightBitShiftCount));
711 /// <summary>
712 /// Performs a Mask operation, but does not perform the shift.
713 /// This is acceptable for boolean values for which the shift is unnecessary
714 /// eg (val &amp; Mask) != 0 is an appropriate way to extract a boolean rather than using
715 /// ((val &amp; Mask) &gt;&gt; shiftAmount) == 1
717 /// ?? is there a common place to put this rather than being private to MRES?
718 /// </summary>
719 /// <param name="state"></param>
720 /// <param name="mask"></param>
721 private static int ExtractStatePortion(int state, int mask)
723 return state & mask;