/// <summary> /// Initializes the internal state of the event. /// </summary> /// <param name="initialState">Whether the event is set initially or not.</param> /// <param name="spinCount">The spin count that decides when the event will block.</param> /// <param name="useDynamicSpinAdjustment">Whether to use dynamic spin count adjustment.</param> private void Initialize(bool initialState, int spinCount, bool useDynamicSpinAdjustment) { m_state = initialState ? EVENT_SIGNALED : EVENT_UNSIGNALED; m_spinCount = /*Environment.ProcessorCount == 1 ? DEFAULT_SPIN_SP :*/ spinCount; if (useDynamicSpinAdjustment) { m_spinCount |= USE_DYNAMIC_SPIN_MASK; } #if DEBUG TraceHelpers.TraceInfo("{0} - tid {1}: ManualResetEventSlim::ctor - created a new event,set={2}", m_id, Thread.CurrentThread.ManagedThreadId, initialState); #endif }
/// <summary> /// Sets the event, possibly waking up any waiting threads. /// </summary> public void Set() { ThrowIfDisposed(); #if DEBUG TraceHelpers.TraceInfo("{0} - tid {1}: ManualResetEventSlim::Set - setting the event", m_id, Thread.CurrentThread.ManagedThreadId); #endif // We need a memory barrier here to ensure the next read of the event object // doesn't occur before the write of the state. This would be a legal movement // according to the .NET memory model. We use an interlocked operation to // acheive this instead of an explicit memory barrier (since it is more // efficient currently on the CLR). #pragma warning disable 0420 Interlocked.Exchange(ref m_state, EVENT_SIGNALED); #pragma warning restore 0420 ManualResetEvent eventObj = m_eventObj; if (eventObj != null) { // We must surround this call to Set in a lock. The reason is fairly subtle. // Sometimes a thread will issue a Wait and wake up after we have set m_state, // but before we have gotten around to setting m_eventObj (just below). That's // because Wait first checks m_state and will only access the event if absolutely // necessary. However, the coding pattern { event.Wait(); event.Dispose() } is // quite common, and we must support it. If the waiter woke up and disposed of // the event object before the setter has finished, however, we would try to set a // now-disposed Win32 event. Crash! To deal with this race, we use a lock to // protect access to the event object when setting and disposing of it. We also // double-check that the event has not become null in the meantime when in the lock. lock (eventObj) { if (m_eventObj != null) { // If somebody is waiting, we must set the event. m_eventObj.Set(); } } } #if DEBUG m_lastSetTime = DateTime.Now.Ticks; #endif }
/// <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); } }
/// <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); }
/// <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); }