Exemple #1
0
        /// <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.
        }
Exemple #2
0
        /// <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.
        }