/// <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="ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is a /// negative number other than -1, which represents an infinite time-out.</exception> /// <exception cref="T:System.Threading.OperationCanceledException"><paramref /// name="cancellationToken"/> was canceled.</exception> public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken) { ThrowIfDisposed(); cancellationToken.ThrowIfCanceled(); // an early convenience check if (millisecondsTimeout < -1) { throw new ArgumentOutOfRangeException("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. SpinWait s = new SpinWait(); long startTimeTicks = 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. startTimeTicks = DateTime.UtcNow.Ticks; bNeedTimeoutAdjustment = true; } //spin for (int i = 0; i < SpinCount; i++) { if (IsSet) { return(true); } s.SpinOnce(); if (i >= 100 && i % 10 == 0) // check the cancellation token if the user passed a very large spin count { cancellationToken.ThrowIfCanceled(); } } // Now enter the lock and wait. EnsureLockObjectCreated(); lock (m_lock) { // Loop to cope with spurious wakeups from other waits being canceled while (!IsSet) { //update timeout (delays in wait commencement are due to spinning and/or spurious wakeups from other waits being canceled) if (bNeedTimeoutAdjustment) { realMillisecondsTimeout = UpdateTimeOut(startTimeTicks, millisecondsTimeout); if (realMillisecondsTimeout <= 0) { return(false); } } // There is a race 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 { // register to listen to the cancellationToken // The event handler must not run before the wait commences. // For the case where the token has not yet been canceled, the event handler is added // and when it is executed on another thread via token.Cancel() it will respect // mutex via m_lock, hence the event handler is delayed until the wait has commenced. // However, if the token is already canceled, the event handler is run // synchronously and is in the context of lock(m_lock). Hence the event handler // is not delayed. To allow for this, we check the token once again after enlisting using (cancellationToken.Register(s_cancellationTokenCallback, this)) { //check the token again. This check is critical (see comments above). cancellationToken.ThrowIfCanceled(); // ** the actual wait ** if (!Monitor.Wait(m_lock, realMillisecondsTimeout)) { return(false); //return immediately if the timeout has expired. } } // automatically disposes (and deregisters) the callback } finally { // Clean up: we're done waiting. Waiters = Waiters - 1; } // Check cancellation after waking up cancellationToken.ThrowIfCanceled(); // if we get here, either // 1. we had a spurious wake-up due to some other wait being canceled via a different cancellationToken (loop around) // or 2. the wait was successful. (the loop will break) } } } return(true); //done. The wait was satisfied. }
/// <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="ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is a /// negative number other than -1, which represents an infinite time-out.</exception> /// <exception cref="T:System.Threading.OperationCanceledException"><paramref /// name="cancellationToken"/> was canceled.</exception> public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken) { ThrowIfDisposed(); cancellationToken.ThrowIfCanceled(); // an early convenience check if (millisecondsTimeout < -1) { throw new ArgumentOutOfRangeException("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. long startTimeTicks = 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. startTimeTicks = DateTime.UtcNow.Ticks; bNeedTimeoutAdjustment = true; } //spin int HOW_MANY_SPIN_BEFORE_YIELD = 10; int HOW_MANY_YIELD_EVERY_SLEEP_0 = 5; int HOW_MANY_YIELD_EVERY_SLEEP_1 = 20; for (int i = 0; i < SpinCount; i++) { if (IsSet) { return(true); } else if (i < HOW_MANY_SPIN_BEFORE_YIELD) { if (i == HOW_MANY_SPIN_BEFORE_YIELD / 2) { #if PFX_LEGACY_3_5 Platform.Yield(); #else Thread.Sleep(0); #endif } else { Thread.SpinWait(Environment.ProcessorCount * (4 << i)); } } else if (i % HOW_MANY_YIELD_EVERY_SLEEP_1 == 0) { Thread.Sleep(1); } else if (i % HOW_MANY_YIELD_EVERY_SLEEP_0 == 0) { Thread.Sleep(0); } else { #if PFX_LEGACY_3_5 Platform.Yield(); #else Thread.Sleep(0); #endif } if (i >= 100 && i % 10 == 0) // check the cancellation token if the user passed a very large spin count { cancellationToken.ThrowIfCanceled(); } } // Now enter the lock and wait. EnsureLockObjectCreated(); // We must register and deregister the token outside of the lock, to avoid deadlocks. using (cancellationToken.Register(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.ThrowIfCanceled(); //update timeout (delays in wait commencement are due to spinning and/or spurious wakeups from other waits being canceled) if (bNeedTimeoutAdjustment) { realMillisecondsTimeout = UpdateTimeOut(startTimeTicks, millisecondsTimeout); if (realMillisecondsTimeout <= 0) { return(false); } } // There is a race 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 deregisters) the callback return(true); //done. The wait was satisfied. }