예제 #1
0
        /// <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()
        {
            int preInitializeState       = m_state;
            ManualResetEvent newEventObj = new ManualResetEvent(preInitializeState == EVENT_SIGNALED);

            // 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)
            {
                // We raced with someone else and lost. Destroy the garbage event.
                newEventObj.Close();

                return(false);
            }
            else
            {
#if DEBUG
                TraceHelpers.TraceInfo("{0} - tid {1}: ManualResetEventSlim::LazyInitializeEvent - created a real event", m_id, Thread.CurrentThread.ManagedThreadId);
#endif

                // 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.
                if (m_state != preInitializeState)
                {
                    TraceHelpers.Assert(IsSet,
                                        "the only safe concurrent transition is from unset->set: detected set->unset");

                    // We saw it as unsignaled, but it has since become set.
                    m_eventObj.Set();
                }

                return(true);
            }
        }
예제 #2
0
        /// <summary>
        /// WaitAny simulates a Win32-style WaitAny on the set of thin-events.
        /// </summary>
        /// <param name="events">An array of thin-events (null elements permitted)</param>
        /// <returns>The index of the specific event in events that caused us to wake up.</returns>
        internal static int WaitAny(ManualResetEventSlim[] events)
        {
            TraceHelpers.Assert(events != null);

#if DEBUG
            string eventList = "";
            foreach (ManualResetEventSlim e in events)
            {
                if (e != null)
                {
                    eventList = eventList + e.m_id + ", ";
                }
            }
            TraceHelpers.TraceInfo("{0}: ManualResetEventSlim::WaitAny - waiting on {1} events ({2})",
                                   Thread.CurrentThread.ManagedThreadId, events.Length, eventList);
#endif

            // First, ensure all the events have been set up.
            int nullEvents = 0;
            for (int i = 0; i < events.Length; i++)
            {
                // We permit nulls in the array -- we just skip them.
                if (events[i] == null)
                {
                    nullEvents++;
                    continue;
                }

                // Throw an exception if the event has been disposed.
                events[i].ThrowIfDisposed();

                // If this event has been set, avoid waiting altogether.
                if (events[i].IsSet)
                {
                    return(i);
                }

                // Otherwise, lazily initialize the event in preparation for waiting.
                if (events[i].m_eventObj == null)
                {
                    events[i].LazyInitializeEvent();
                }
            }

            // Lastly, accumulate the events in preparation for a true wait.
            WaitHandle[] waitHandles = new WaitHandle[events.Length - nullEvents];
            TraceHelpers.Assert(waitHandles.Length > 0);

            for (int i = 0, j = 0; i < events.Length; i++)
            {
                if (events[i] == null)
                {
                    continue;
                }

                waitHandles[j] = events[i].WaitHandle;
                j++;
            }

            // And finally, issue the real wait.
            int index = WaitHandle.WaitAny(waitHandles);

            // Translate this back into the events array index. The 'waitHandles' array
            // will effectively have the non-null elements "slid down" into the positions
            // from 'events' that contain nulls. We count the number of null handles before
            // the index and add that to get our real position.
            for (int i = 0, j = -1; i < events.Length; i++)
            {
                // If the current event is non-null, increment our translation index.
                if (events[i] != null)
                {
                    j++;

                    // If we found the element, adjust our index and break.
                    if (j == index)
                    {
                        index = i;
                        break;
                    }
                }

                TraceHelpers.Assert(i != events.Length - 1, "didn't find a non-null event");
            }
            TraceHelpers.Assert(events[index] != null, "expected non-null event");

            return(index);
        }
예제 #3
0
        /// <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);
        }