| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759 |
- // Licensed to the .NET Foundation under one or more agreements.
- // The .NET Foundation licenses this file to you under the MIT license.
- // See the LICENSE file in the project root for more information.
- using System.Diagnostics;
- namespace System.Threading
- {
- // ManualResetEventSlim wraps a manual-reset event internally with a little bit of
- // spinning. When an event will be set imminently, it is often advantageous to avoid
- // a 4k+ cycle context switch in favor of briefly spinning. Therefore we layer on to
- // a brief amount of spinning that should, on the average, make using the slim event
- // cheaper than using Win32 events directly. This can be reset manually, much like
- // a Win32 manual-reset would be.
- //
- // Notes:
- // We lazily allocate the Win32 event internally. Therefore, the caller should
- // always call Dispose to clean it up, just in case. This API is a no-op of the
- // event wasn't allocated, but if it was, ensures that the event goes away
- // eagerly, instead of waiting for finalization.
- /// <summary>
- /// Provides a slimmed down version of <see cref="T:System.Threading.ManualResetEvent"/>.
- /// </summary>
- /// <remarks>
- /// All public and protected members of <see cref="ManualResetEventSlim"/> are thread-safe and may be used
- /// concurrently from multiple threads, with the exception of Dispose, which
- /// must only be used when all other operations on the <see cref="ManualResetEventSlim"/> have
- /// completed, and Reset, which should only be used when no other threads are
- /// accessing the event.
- /// </remarks>
- [DebuggerDisplay("Set = {IsSet}")]
- public class ManualResetEventSlim : IDisposable
- {
- // These are the default spin counts we use on single-proc and MP machines.
- private const int DEFAULT_SPIN_SP = 1;
- private volatile object m_lock;
- // A lock used for waiting and pulsing. Lazily initialized via EnsureLockObjectCreated()
- private volatile ManualResetEvent m_eventObj; // A true Win32 event used for waiting.
- // -- State -- //
- //For a packed word a uint would seem better, but Interlocked.* doesn't support them as uint isn't CLS-compliant.
- private volatile int m_combinedState; //ie a uint. Used for the state items listed below.
- //1-bit for signalled state
- private const int SignalledState_BitMask = unchecked((int)0x80000000);//1000 0000 0000 0000 0000 0000 0000 0000
- private const int SignalledState_ShiftCount = 31;
- //1-bit for disposed state
- private const int Dispose_BitMask = unchecked((int)0x40000000);//0100 0000 0000 0000 0000 0000 0000 0000
- //11-bits for m_spinCount
- private const int SpinCountState_BitMask = unchecked((int)0x3FF80000); //0011 1111 1111 1000 0000 0000 0000 0000
- private const int SpinCountState_ShiftCount = 19;
- private const int SpinCountState_MaxValue = (1 << 11) - 1; //2047
- //19-bits for m_waiters. This allows support of 512K threads waiting which should be ample
- private const int NumWaitersState_BitMask = unchecked((int)0x0007FFFF); // 0000 0000 0000 0111 1111 1111 1111 1111
- private const int NumWaitersState_ShiftCount = 0;
- private const int NumWaitersState_MaxValue = (1 << 19) - 1; //512K-1
- // ----------- //
- #if DEBUG
- private static int s_nextId; // The next id that will be given out.
- private int m_id = Interlocked.Increment(ref s_nextId); // A unique id for debugging purposes only.
- private long m_lastSetTime;
- private long m_lastResetTime;
- #endif
- /// <summary>
- /// Gets the underlying <see cref="T:System.Threading.WaitHandle"/> object for this <see
- /// cref="ManualResetEventSlim"/>.
- /// </summary>
- /// <value>The underlying <see cref="T:System.Threading.WaitHandle"/> event object fore this <see
- /// cref="ManualResetEventSlim"/>.</value>
- /// <remarks>
- /// Accessing this property forces initialization of an underlying event object if one hasn't
- /// already been created. To simply wait on this <see cref="ManualResetEventSlim"/>,
- /// the public Wait methods should be preferred.
- /// </remarks>
- public WaitHandle WaitHandle
- {
- get
- {
- ThrowIfDisposed();
- if (m_eventObj == null)
- {
- // Lazily initialize the event object if needed.
- LazyInitializeEvent();
- }
- return m_eventObj;
- }
- }
- /// <summary>
- /// Gets whether the event is set.
- /// </summary>
- /// <value>true if the event has is set; otherwise, false.</value>
- public bool IsSet
- {
- get
- {
- return 0 != ExtractStatePortion(m_combinedState, SignalledState_BitMask);
- }
- private set
- {
- UpdateStateAtomically(((value) ? 1 : 0) << SignalledState_ShiftCount, SignalledState_BitMask);
- }
- }
- /// <summary>
- /// Gets the number of spin waits that will be occur before falling back to a true wait.
- /// </summary>
- public int SpinCount
- {
- get
- {
- return ExtractStatePortionAndShiftRight(m_combinedState, SpinCountState_BitMask, SpinCountState_ShiftCount);
- }
- private set
- {
- Debug.Assert(value >= 0, "SpinCount is a restricted-width integer. The value supplied is outside the legal range.");
- Debug.Assert(value <= SpinCountState_MaxValue, "SpinCount is a restricted-width integer. The value supplied is outside the legal range.");
- // Don't worry about thread safety because it's set one time from the constructor
- m_combinedState = (m_combinedState & ~SpinCountState_BitMask) | (value << SpinCountState_ShiftCount);
- }
- }
- /// <summary>
- /// How many threads are waiting.
- /// </summary>
- private int Waiters
- {
- get
- {
- return ExtractStatePortionAndShiftRight(m_combinedState, NumWaitersState_BitMask, NumWaitersState_ShiftCount);
- }
- set
- {
- //setting to <0 would indicate an internal flaw, hence Assert is appropriate.
- Debug.Assert(value >= 0, "NumWaiters should never be less than zero. This indicates an internal error.");
- // it is possible for the max number of waiters to be exceeded via user-code, hence we use a real exception here.
- if (value >= NumWaitersState_MaxValue)
- throw new InvalidOperationException(SR.Format(SR.ManualResetEventSlim_ctor_TooManyWaiters, NumWaitersState_MaxValue));
- UpdateStateAtomically(value << NumWaitersState_ShiftCount, NumWaitersState_BitMask);
- }
- }
- //-----------------------------------------------------------------------------------
- // Constructs a new event, optionally specifying the initial state and spin count.
- // The defaults are that the event is unsignaled and some reasonable default spin.
- //
- /// <summary>
- /// Initializes a new instance of the <see cref="ManualResetEventSlim"/>
- /// class with an initial state of nonsignaled.
- /// </summary>
- public ManualResetEventSlim()
- : this(false)
- {
- }
- /// <summary>
- /// Initializes a new instance of the <see cref="ManualResetEventSlim"/>
- /// class with a boolean value indicating whether to set the initial state to signaled.
- /// </summary>
- /// <param name="initialState">true to set the initial state signaled; false to set the initial state
- /// to nonsignaled.</param>
- public ManualResetEventSlim(bool initialState)
- {
- // Specify the default spin count, and use default spin if we're
- // on a multi-processor machine. Otherwise, we won't.
- Initialize(initialState, SpinWait.SpinCountforSpinBeforeWait);
- }
- /// <summary>
- /// Initializes a new instance of the <see cref="ManualResetEventSlim"/>
- /// class with a Boolean value indicating whether to set the initial state to signaled and a specified
- /// spin count.
- /// </summary>
- /// <param name="initialState">true to set the initial state to signaled; false to set the initial state
- /// to nonsignaled.</param>
- /// <param name="spinCount">The number of spin waits that will occur before falling back to a true
- /// wait.</param>
- /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="spinCount"/> is less than
- /// 0 or greater than the maximum allowed value.</exception>
- public ManualResetEventSlim(bool initialState, int spinCount)
- {
- if (spinCount < 0)
- {
- throw new ArgumentOutOfRangeException(nameof(spinCount));
- }
- if (spinCount > SpinCountState_MaxValue)
- {
- throw new ArgumentOutOfRangeException(
- nameof(spinCount),
- SR.Format(SR.ManualResetEventSlim_ctor_SpinCountOutOfRange, SpinCountState_MaxValue));
- }
- // We will suppress default spin because the user specified a count.
- Initialize(initialState, spinCount);
- }
- /// <summary>
- /// Initializes the internal state of the event.
- /// </summary>
- /// <param name="initialState">Whether the event is set initially or not.</param>
- /// <param name="spinCount">The spin count that decides when the event will block.</param>
- private void Initialize(bool initialState, int spinCount)
- {
- m_combinedState = initialState ? (1 << SignalledState_ShiftCount) : 0;
- //the spinCount argument has been validated by the ctors.
- //but we now sanity check our predefined constants.
- Debug.Assert(DEFAULT_SPIN_SP >= 0, "Internal error - DEFAULT_SPIN_SP is outside the legal range.");
- Debug.Assert(DEFAULT_SPIN_SP <= SpinCountState_MaxValue, "Internal error - DEFAULT_SPIN_SP is outside the legal range.");
- SpinCount = PlatformHelper.IsSingleProcessor ? DEFAULT_SPIN_SP : spinCount;
- }
- /// <summary>
- /// Helper to ensure the lock object is created before first use.
- /// </summary>
- private void EnsureLockObjectCreated()
- {
- if (m_lock != null)
- return;
- object newObj = new object();
- Interlocked.CompareExchange(ref m_lock, newObj, null); // failure is benign. Someone else set the value.
- }
- /// <summary>
- /// This method lazily initializes the event object. It uses CAS to guarantee that
- /// many threads racing to call this at once don't result in more than one event
- /// being stored and used. The event will be signaled or unsignaled depending on
- /// the state of the thin-event itself, with synchronization taken into account.
- /// </summary>
- /// <returns>True if a new event was created and stored, false otherwise.</returns>
- private bool LazyInitializeEvent()
- {
- bool preInitializeIsSet = IsSet;
- ManualResetEvent newEventObj = new ManualResetEvent(preInitializeIsSet);
- // We have to CAS this in case we are racing with another thread. We must
- // guarantee only one event is actually stored in this field.
- if (Interlocked.CompareExchange(ref m_eventObj, newEventObj, null) != null)
- {
- // Someone else set the value due to a race condition. Destroy the garbage event.
- newEventObj.Dispose();
- return false;
- }
- else
- {
- // Now that the event is published, verify that the state hasn't changed since
- // we snapped the preInitializeState. Another thread could have done that
- // between our initial observation above and here. The barrier incurred from
- // the CAS above (in addition to m_state being volatile) prevents this read
- // from moving earlier and being collapsed with our original one.
- bool currentIsSet = IsSet;
- if (currentIsSet != preInitializeIsSet)
- {
- Debug.Assert(currentIsSet,
- "The only safe concurrent transition is from unset->set: detected set->unset.");
- // We saw it as unsignaled, but it has since become set.
- lock (newEventObj)
- {
- // If our event hasn't already been disposed of, we must set it.
- if (m_eventObj == newEventObj)
- {
- newEventObj.Set();
- }
- }
- }
- return true;
- }
- }
- /// <summary>
- /// Sets the state of the event to signaled, which allows one or more threads waiting on the event to
- /// proceed.
- /// </summary>
- public void Set()
- {
- Set(false);
- }
- /// <summary>
- /// Private helper to actually perform the Set.
- /// </summary>
- /// <param name="duringCancellation">Indicates whether we are calling Set() during cancellation.</param>
- /// <exception cref="T:System.OperationCanceledException">The object has been canceled.</exception>
- private void Set(bool duringCancellation)
- {
- // We need to ensure that IsSet=true does not get reordered past the read of m_eventObj
- // This would be a legal movement according to the .NET memory model.
- // The code is safe as IsSet involves an Interlocked.CompareExchange which provides a full memory barrier.
- IsSet = true;
- // If there are waiting threads, we need to pulse them.
- if (Waiters > 0)
- {
- Debug.Assert(m_lock != null); //if waiters>0, then m_lock has already been created.
- lock (m_lock)
- {
- Monitor.PulseAll(m_lock);
- }
- }
- ManualResetEvent eventObj = m_eventObj;
- //Design-decision: do not set the event if we are in cancellation -> better to deadlock than to wake up waiters incorrectly
- //It would be preferable to wake up the event and have it throw OCE. This requires MRE to implement cancellation logic
- if (eventObj != null && !duringCancellation)
- {
- // We must surround this call to Set in a lock. The reason is fairly subtle.
- // Sometimes a thread will issue a Wait and wake up after we have set m_state,
- // but before we have gotten around to setting m_eventObj (just below). That's
- // because Wait first checks m_state and will only access the event if absolutely
- // necessary. However, the coding pattern { event.Wait(); event.Dispose() } is
- // quite common, and we must support it. If the waiter woke up and disposed of
- // the event object before the setter has finished, however, we would try to set a
- // now-disposed Win32 event. Crash! To deal with this race condition, we use a lock to
- // protect access to the event object when setting and disposing of it. We also
- // double-check that the event has not become null in the meantime when in the lock.
- lock (eventObj)
- {
- if (m_eventObj != null)
- {
- // If somebody is waiting, we must set the event.
- m_eventObj.Set();
- }
- }
- }
- #if DEBUG
- m_lastSetTime = DateTime.UtcNow.Ticks;
- #endif
- }
- /// <summary>
- /// Sets the state of the event to nonsignaled, which causes threads to block.
- /// </summary>
- /// <remarks>
- /// Unlike most of the members of <see cref="ManualResetEventSlim"/>, <see cref="Reset()"/> is not
- /// thread-safe and may not be used concurrently with other members of this instance.
- /// </remarks>
- public void Reset()
- {
- ThrowIfDisposed();
- // If there's an event, reset it.
- if (m_eventObj != null)
- {
- m_eventObj.Reset();
- }
- // There is a race condition here. If another thread Sets the event, we will get into a state
- // where m_state will be unsignaled, yet the Win32 event object will have been signaled.
- // This could cause waiting threads to wake up even though the event is in an
- // unsignaled state. This is fine -- those that are calling Reset concurrently are
- // responsible for doing "the right thing" -- e.g. rechecking the condition and
- // resetting the event manually.
- // And finally set our state back to unsignaled.
- IsSet = false;
- #if DEBUG
- m_lastResetTime = DateTime.UtcNow.Ticks;
- #endif
- }
- /// <summary>
- /// Blocks the current thread until the current <see cref="ManualResetEventSlim"/> is set.
- /// </summary>
- /// <exception cref="T:System.InvalidOperationException">
- /// The maximum number of waiters has been exceeded.
- /// </exception>
- /// <remarks>
- /// The caller of this method blocks indefinitely until the current instance is set. The caller will
- /// return immediately if the event is currently in a set state.
- /// </remarks>
- public void Wait()
- {
- Wait(Timeout.Infinite, new CancellationToken());
- }
- /// <summary>
- /// Blocks the current thread until the current <see cref="ManualResetEventSlim"/> receives a signal,
- /// while observing a <see cref="T:System.Threading.CancellationToken"/>.
- /// </summary>
- /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> to
- /// observe.</param>
- /// <exception cref="T:System.InvalidOperationException">
- /// The maximum number of waiters has been exceeded.
- /// </exception>
- /// <exception cref="T:System.OperationCanceledExcepton"><paramref name="cancellationToken"/> was
- /// canceled.</exception>
- /// <remarks>
- /// The caller of this method blocks indefinitely until the current instance is set. The caller will
- /// return immediately if the event is currently in a set state.
- /// </remarks>
- public void Wait(CancellationToken cancellationToken)
- {
- Wait(Timeout.Infinite, cancellationToken);
- }
- /// <summary>
- /// Blocks the current thread until the current <see cref="ManualResetEventSlim"/> is set, using a
- /// <see cref="T:System.TimeSpan"/> to measure the time interval.
- /// </summary>
- /// <param name="timeout">A <see cref="System.TimeSpan"/> that represents the number of milliseconds
- /// to wait, or a <see cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely.
- /// </param>
- /// <returns>true if the <see cref="System.Threading.ManualResetEventSlim"/> was set; otherwise,
- /// false.</returns>
- /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="timeout"/> is a negative
- /// number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater
- /// than <see cref="System.Int32.MaxValue"/>.</exception>
- /// <exception cref="T:System.InvalidOperationException">
- /// The maximum number of waiters has been exceeded.
- /// </exception>
- public bool Wait(TimeSpan timeout)
- {
- long totalMilliseconds = (long)timeout.TotalMilliseconds;
- if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue)
- {
- throw new ArgumentOutOfRangeException(nameof(timeout));
- }
- return Wait((int)totalMilliseconds, new CancellationToken());
- }
- /// <summary>
- /// Blocks the current thread until the current <see cref="ManualResetEventSlim"/> is set, using a
- /// <see cref="T:System.TimeSpan"/> to measure the time interval, while observing a <see
- /// cref="T:System.Threading.CancellationToken"/>.
- /// </summary>
- /// <param name="timeout">A <see cref="System.TimeSpan"/> that represents the number of milliseconds
- /// to wait, or a <see cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely.
- /// </param>
- /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> to
- /// observe.</param>
- /// <returns>true if the <see cref="System.Threading.ManualResetEventSlim"/> was set; otherwise,
- /// false.</returns>
- /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="timeout"/> is a negative
- /// number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater
- /// than <see cref="System.Int32.MaxValue"/>.</exception>
- /// <exception cref="T:System.Threading.OperationCanceledException"><paramref
- /// name="cancellationToken"/> was canceled.</exception>
- /// <exception cref="T:System.InvalidOperationException">
- /// The maximum number of waiters has been exceeded.
- /// </exception>
- public bool Wait(TimeSpan timeout, CancellationToken cancellationToken)
- {
- long totalMilliseconds = (long)timeout.TotalMilliseconds;
- if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue)
- {
- throw new ArgumentOutOfRangeException(nameof(timeout));
- }
- return Wait((int)totalMilliseconds, cancellationToken);
- }
- /// <summary>
- /// Blocks the current thread until the current <see cref="ManualResetEventSlim"/> is set, using a
- /// 32-bit signed integer to measure the time interval.
- /// </summary>
- /// <param name="millisecondsTimeout">The number of milliseconds to wait, or <see
- /// cref="Timeout.Infinite"/>(-1) to wait indefinitely.</param>
- /// <returns>true if the <see cref="System.Threading.ManualResetEventSlim"/> was set; otherwise,
- /// false.</returns>
- /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is a
- /// negative number other than -1, which represents an infinite time-out.</exception>
- /// <exception cref="T:System.InvalidOperationException">
- /// The maximum number of waiters has been exceeded.
- /// </exception>
- public bool Wait(int millisecondsTimeout)
- {
- return Wait(millisecondsTimeout, new CancellationToken());
- }
- /// <summary>
- /// Blocks the current thread until the current <see cref="ManualResetEventSlim"/> is set, using a
- /// 32-bit signed integer to measure the time interval, while observing a <see
- /// cref="T:System.Threading.CancellationToken"/>.
- /// </summary>
- /// <param name="millisecondsTimeout">The number of milliseconds to wait, or <see
- /// cref="Timeout.Infinite"/>(-1) to wait indefinitely.</param>
- /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> to
- /// observe.</param>
- /// <returns>true if the <see cref="System.Threading.ManualResetEventSlim"/> was set; otherwise,
- /// false.</returns>
- /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is a
- /// negative number other than -1, which represents an infinite time-out.</exception>
- /// <exception cref="T:System.InvalidOperationException">
- /// The maximum number of waiters has been exceeded.
- /// </exception>
- /// <exception cref="T:System.Threading.OperationCanceledException"><paramref
- /// name="cancellationToken"/> was canceled.</exception>
- public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
- {
- ThrowIfDisposed();
- cancellationToken.ThrowIfCancellationRequested(); // an early convenience check
- if (millisecondsTimeout < -1)
- {
- throw new ArgumentOutOfRangeException(nameof(millisecondsTimeout));
- }
- if (!IsSet)
- {
- if (millisecondsTimeout == 0)
- {
- // For 0-timeouts, we just return immediately.
- return false;
- }
- // We spin briefly before falling back to allocating and/or waiting on a true event.
- uint startTime = 0;
- bool bNeedTimeoutAdjustment = false;
- int realMillisecondsTimeout = millisecondsTimeout; //this will be adjusted if necessary.
- if (millisecondsTimeout != Timeout.Infinite)
- {
- // We will account for time spent spinning, so that we can decrement it from our
- // timeout. In most cases the time spent in this section will be negligible. But
- // we can't discount the possibility of our thread being switched out for a lengthy
- // period of time. The timeout adjustments only take effect when and if we actually
- // decide to block in the kernel below.
- startTime = TimeoutHelper.GetTime();
- bNeedTimeoutAdjustment = true;
- }
- // Spin
- int spinCount = SpinCount;
- var spinner = new SpinWait();
- while (spinner.Count < spinCount)
- {
- spinner.SpinOnce(sleep1Threshold: -1);
- if (IsSet)
- {
- return true;
- }
- if (spinner.Count >= 100 && spinner.Count % 10 == 0) // check the cancellation token if the user passed a very large spin count
- cancellationToken.ThrowIfCancellationRequested();
- }
- // Now enter the lock and wait.
- EnsureLockObjectCreated();
- // We must register and unregister the token outside of the lock, to avoid deadlocks.
- using (cancellationToken.UnsafeRegister(s_cancellationTokenCallback, this))
- {
- lock (m_lock)
- {
- // Loop to cope with spurious wakeups from other waits being canceled
- while (!IsSet)
- {
- // If our token was canceled, we must throw and exit.
- cancellationToken.ThrowIfCancellationRequested();
- //update timeout (delays in wait commencement are due to spinning and/or spurious wakeups from other waits being canceled)
- if (bNeedTimeoutAdjustment)
- {
- realMillisecondsTimeout = TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout);
- if (realMillisecondsTimeout <= 0)
- return false;
- }
- // There is a race condition that Set will fail to see that there are waiters as Set does not take the lock,
- // so after updating waiters, we must check IsSet again.
- // Also, we must ensure there cannot be any reordering of the assignment to Waiters and the
- // read from IsSet. This is guaranteed as Waiters{set;} involves an Interlocked.CompareExchange
- // operation which provides a full memory barrier.
- // If we see IsSet=false, then we are guaranteed that Set() will see that we are
- // waiting and will pulse the monitor correctly.
- Waiters = Waiters + 1;
- if (IsSet) //This check must occur after updating Waiters.
- {
- Waiters--; //revert the increment.
- return true;
- }
- // Now finally perform the wait.
- try
- {
- // ** the actual wait **
- if (!Monitor.Wait(m_lock, realMillisecondsTimeout))
- return false; //return immediately if the timeout has expired.
- }
- finally
- {
- // Clean up: we're done waiting.
- Waiters = Waiters - 1;
- }
- // Now just loop back around, and the right thing will happen. Either:
- // 1. We had a spurious wake-up due to some other wait being canceled via a different cancellationToken (rewait)
- // or 2. the wait was successful. (the loop will break)
- }
- }
- }
- } // automatically disposes (and unregisters) the callback
- return true; //done. The wait was satisfied.
- }
- /// <summary>
- /// Releases all resources used by the current instance of <see cref="ManualResetEventSlim"/>.
- /// </summary>
- /// <remarks>
- /// Unlike most of the members of <see cref="ManualResetEventSlim"/>, <see cref="Dispose()"/> is not
- /// thread-safe and may not be used concurrently with other members of this instance.
- /// </remarks>
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
- /// <summary>
- /// When overridden in a derived class, releases the unmanaged resources used by the
- /// <see cref="ManualResetEventSlim"/>, and optionally releases the managed resources.
- /// </summary>
- /// <param name="disposing">true to release both managed and unmanaged resources;
- /// false to release only unmanaged resources.</param>
- /// <remarks>
- /// Unlike most of the members of <see cref="ManualResetEventSlim"/>, <see cref="Dispose(bool)"/> is not
- /// thread-safe and may not be used concurrently with other members of this instance.
- /// </remarks>
- protected virtual void Dispose(bool disposing)
- {
- if ((m_combinedState & Dispose_BitMask) != 0)
- return; // already disposed
- m_combinedState |= Dispose_BitMask; //set the dispose bit
- if (disposing)
- {
- // We will dispose of the event object. We do this under a lock to protect
- // against the race condition outlined in the Set method above.
- ManualResetEvent eventObj = m_eventObj;
- if (eventObj != null)
- {
- lock (eventObj)
- {
- eventObj.Dispose();
- m_eventObj = null;
- }
- }
- }
- }
- /// <summary>
- /// Throw ObjectDisposedException if the MRES is disposed
- /// </summary>
- private void ThrowIfDisposed()
- {
- if ((m_combinedState & Dispose_BitMask) != 0)
- throw new ObjectDisposedException(SR.ManualResetEventSlim_Disposed);
- }
- /// <summary>
- /// Private helper method to wake up waiters when a cancellationToken gets canceled.
- /// </summary>
- private static Action<object> s_cancellationTokenCallback = new Action<object>(CancellationTokenCallback);
- private static void CancellationTokenCallback(object obj)
- {
- ManualResetEventSlim mre = obj as ManualResetEventSlim;
- Debug.Assert(mre != null, "Expected a ManualResetEventSlim");
- Debug.Assert(mre.m_lock != null); //the lock should have been created before this callback is registered for use.
- lock (mre.m_lock)
- {
- Monitor.PulseAll(mre.m_lock); // awaken all waiters
- }
- }
- /// <summary>
- /// Private helper method for updating parts of a bit-string state value.
- /// Mainly called from the IsSet and Waiters properties setters
- /// </summary>
- /// <remarks>
- /// Note: the parameter types must be int as CompareExchange cannot take a Uint
- /// </remarks>
- /// <param name="newBits">The new value</param>
- /// <param name="updateBitsMask">The mask used to set the bits</param>
- private void UpdateStateAtomically(int newBits, int updateBitsMask)
- {
- SpinWait sw = new SpinWait();
- Debug.Assert((newBits | updateBitsMask) == updateBitsMask, "newBits do not fall within the updateBitsMask.");
- do
- {
- int oldState = m_combinedState; // cache the old value for testing in CAS
- // Procedure:(1) zero the updateBits. eg oldState = [11111111] flag= [00111000] newState = [11000111]
- // then (2) map in the newBits. eg [11000111] newBits=00101000, newState=[11101111]
- int newState = (oldState & ~updateBitsMask) | newBits;
- if (Interlocked.CompareExchange(ref m_combinedState, newState, oldState) == oldState)
- {
- return;
- }
- sw.SpinOnce(sleep1Threshold: -1);
- } while (true);
- }
- /// <summary>
- /// Private helper method - performs Mask and shift, particular helpful to extract a field from a packed word.
- /// eg ExtractStatePortionAndShiftRight(0x12345678, 0xFF000000, 24) => 0x12, ie extracting the top 8-bits as a simple integer
- ///
- /// ?? is there a common place to put this rather than being private to MRES?
- /// </summary>
- /// <param name="state"></param>
- /// <param name="mask"></param>
- /// <param name="rightBitShiftCount"></param>
- /// <returns></returns>
- private static int ExtractStatePortionAndShiftRight(int state, int mask, int rightBitShiftCount)
- {
- //convert to uint before shifting so that right-shift does not replicate the sign-bit,
- //then convert back to int.
- return unchecked((int)(((uint)(state & mask)) >> rightBitShiftCount));
- }
- /// <summary>
- /// Performs a Mask operation, but does not perform the shift.
- /// This is acceptable for boolean values for which the shift is unnecessary
- /// eg (val & Mask) != 0 is an appropriate way to extract a boolean rather than using
- /// ((val & Mask) >> shiftAmount) == 1
- ///
- /// ?? is there a common place to put this rather than being private to MRES?
- /// </summary>
- /// <param name="state"></param>
- /// <param name="mask"></param>
- private static int ExtractStatePortion(int state, int mask)
- {
- return state & mask;
- }
- }
- }
|