/// <summary> /// Waits for the event to become set. We will spin briefly if the event isn't set /// (assuming a non-0 timeout), before falling back to a true wait. /// </summary> /// <param name="millisecondsTimeout">The maximum amount of time to wait.</param> /// <returns>True if the wait succeeded, false if the timeout expired.</returns> /// <exception cref="System.ArgumentOutOfRangeException">If the timeout is not within range.</exception> public bool Wait(int millisecondsTimeout) { ThrowIfDisposed(); if (millisecondsTimeout < -1) { throw Error.ArgumentOutOfRange("millisecondsTimeout"); } if (!IsSet) { if (millisecondsTimeout == 0) { // For 0-timeouts, we just return immediately. return(false); } else { #if DEBUG TraceHelpers.TraceInfo("{0} - tid {1}: ManualResetEventSlim::Wait - doing a spin wait for {2} spins", m_id, Thread.CurrentThread.ManagedThreadId, SpinCount); #endif // We spin briefly before falling back to allocating and/or waiting on a true event. SpinWait s = new SpinWait(); Stopwatch sw = null; if (SpinCount > 0) { 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. sw = Stopwatch.StartNew(); } for (int i = 0; i < SpinCount; i++) { s.SpinOnce(); // Keep rechecking the state. If we've become signaled while we spun above, // return. This is done with a volatile read to prevent reordering and hoisting // out of the loop. if (IsSet) { return(true); } } } if (m_eventObj == null) { // Lazily allocate the event. This method internally handles races w/ other threads. LazyInitializeEvent(); if (IsSet) { // If it has since become signaled, there's no need to wait. This is strictly // unnecessary, but does avoid a kernel transition. Since the allocation could // have taken some time, this might be worth the extra check in some cases. return(true); } } #if DEBUG TraceHelpers.TraceInfo("{0} - tid {1}: ManualResetEventSlim::Wait - entering a real wait", m_id, Thread.CurrentThread.ManagedThreadId); #endif // Do our dynamic spin count accounting. We don't worry about thread safety here. // It's OK for reads and writes to be carried out non-atomically -- we might miss one // here or there, but because it's a 4-byte aligned value we are guaranteed no tears. if (UseDynamicSpinAdjustment) { int spinCount = SpinCount; if (spinCount != 0 && spinCount < MAX_DYNAMIC_SPIN) { // We use an algorithm similar to the OS critical section. If our // spinning didn't work out, we increment the counter so that we increase // the chances of spinning working the next time. m_spinCount = ((spinCount + 1) % int.MaxValue) | USE_DYNAMIC_SPIN_MASK; } else { // If we reached the dynamic spin maximum, and it's still not working, bail. // We don't want to spin any longer since it's not buying us anything (in fact, // it's just wasting time). This forces us to go straight to the wait next time. m_spinCount = 0; } } // If we spun at all, we'll take into account the time elapsed by adjusting // the timeout value before blocking in the kernel. int realMillisecondsTimeout = millisecondsTimeout; if (sw != null) { long elapsedMilliseconds = sw.ElapsedMilliseconds; if (elapsedMilliseconds > int.MaxValue) { return(false); } TraceHelpers.Assert(elapsedMilliseconds >= 0); realMillisecondsTimeout -= (int)elapsedMilliseconds; if (realMillisecondsTimeout <= 0) { return(false); } } return(m_eventObj.WaitOne(realMillisecondsTimeout, false)); } } return(true); }