/// <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 HOW_MANY_SPIN_BEFORE_YIELD = 10;
int HOW_MANY_YIELD_EVERY_SLEEP_0 = 5;
int HOW_MANY_YIELD_EVERY_SLEEP_1 = 20;
int spinCount = SpinCount;
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)
{
Thread.Yield();
}
else
{
Thread.SpinWait(PlatformHelper.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
{
Thread.Yield();
}
if (i >= 100 && i % 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 deregister the token outside of the lock, to avoid deadlocks.
using (cancellationToken.InternalRegisterWithoutEC(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 deregisters) the callback
return(true); //done. The wait was satisfied.
}