// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ // // // // A simple coordination data structure that we use for fork/join style parallelism. // // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- using System; using System.Security.Permissions; using System.Runtime.InteropServices; using System.Threading; using System.Diagnostics; using System.Diagnostics.Contracts; namespace System.Threading { /// /// Represents a synchronization primitive that is signaled when its count reaches zero. /// /// /// All public and protected members of are thread-safe and may be used /// concurrently from multiple threads, with the exception of Dispose, which /// must only be used when all other operations on the have /// completed, and Reset, which should only be used when no other threads are /// accessing the event. /// [ComVisible(false)] [DebuggerDisplay("Initial Count={InitialCount}, Current Count={CurrentCount}")] public class CountdownEvent : IDisposable { // CountdownEvent is a simple synchronization primitive used for fork/join parallelism. We create a // latch with a count of N; threads then signal the latch, which decrements N by 1; other threads can // wait on the latch at any point; when the latch count reaches 0, all threads are woken and // subsequent waiters return without waiting. The implementation internally lazily creates a true // Win32 event as needed. We also use some amount of spinning on MP machines before falling back to a // wait. private int m_initialCount; // The original # of signals the latch was instantiated with. private volatile int m_currentCount; // The # of outstanding signals before the latch transitions to a signaled state. private ManualResetEventSlim m_event; // An event used to manage blocking and signaling. private volatile bool m_disposed; // Whether the latch has been disposed. /// /// Initializes a new instance of class with the /// specified count. /// /// The number of signals required to set the . /// is less /// than 0. public CountdownEvent(int initialCount) { if (initialCount < 0) { throw new ArgumentOutOfRangeException(nameof(initialCount)); } m_initialCount = initialCount; m_currentCount = initialCount; // Allocate a thin event, which internally defers creation of an actual Win32 event. m_event = new ManualResetEventSlim(); // If the latch was created with a count of 0, then it's already in the signaled state. if (initialCount == 0) { m_event.Set(); } } /// /// Gets the number of remaining signals required to set the event. /// /// /// The number of remaining signals required to set the event. /// public int CurrentCount { get { int observedCount = m_currentCount; return observedCount < 0 ? 0 : observedCount; } } /// /// Gets the numbers of signals initially required to set the event. /// /// /// The number of signals initially required to set the event. /// public int InitialCount { get { return m_initialCount; } } /// /// Determines whether the event is set. /// /// true if the event is set; otherwise, false. public bool IsSet { get { // The latch is "completed" if its current count has reached 0. Note that this is NOT // the same thing is checking the event's IsCompleted property. There is a tiny window // of time, after the final decrement of the current count to 0 and before setting the // event, where the two values are out of sync. return (m_currentCount <= 0); } } /// /// Gets a that is used to wait for the event to be set. /// /// A that is used to wait for the event to be set. /// The current instance has already been disposed. /// /// should only be used if it's needed for integration with code bases /// that rely on having a WaitHandle. If all that's needed is to wait for the /// to be set, the method should be preferred. /// public WaitHandle WaitHandle { get { ThrowIfDisposed(); return m_event.WaitHandle; } } /// /// Releases all resources used by the current instance of . /// /// /// Unlike most of the members of , is not /// thread-safe and may not be used concurrently with other members of this instance. /// public void Dispose() { // Gets rid of this latch's associated resources. This can consist of a Win32 event // which is (lazily) allocated by the underlying thin event. This method is not safe to // call concurrently -- i.e. a caller must coordinate to ensure only one thread is using // the latch at the time of the call to Dispose. Dispose(true); GC.SuppressFinalize(this); } /// /// When overridden in a derived class, releases the unmanaged resources used by the /// , and optionally releases the managed resources. /// /// true to release both managed and unmanaged resources; false to release /// only unmanaged resources. /// /// Unlike most of the members of , is not /// thread-safe and may not be used concurrently with other members of this instance. /// protected virtual void Dispose(bool disposing) { if (disposing) { m_event.Dispose(); m_disposed = true; } } /// /// Registers a signal with the , decrementing its /// count. /// /// true if the signal caused the count to reach zero and the event was set; otherwise, /// false. /// The current instance is already set. /// /// The current instance has already been /// disposed. public bool Signal() { ThrowIfDisposed(); Debug.Assert(m_event != null); if (m_currentCount <= 0) { throw new InvalidOperationException(Environment.GetResourceString("CountdownEvent_Decrement_BelowZero")); } #pragma warning disable 0420 int newCount = Interlocked.Decrement(ref m_currentCount); #pragma warning restore 0420 if (newCount == 0) { m_event.Set(); return true; } else if (newCount < 0) { //if the count is decremented below zero, then throw, it's OK to keep the count negative, and we shouldn't set the event here //because there was a thread already which decremented it to zero and set the event throw new InvalidOperationException(Environment.GetResourceString("CountdownEvent_Decrement_BelowZero")); } return false; } /// /// Registers multiple signals with the , /// decrementing its count by the specified amount. /// /// The number of signals to register. /// true if the signals caused the count to reach zero and the event was set; otherwise, /// false. /// /// The current instance is already set. -or- Or is greater than . /// /// is less /// than 1. /// The current instance has already been /// disposed. public bool Signal(int signalCount) { if (signalCount <= 0) { throw new ArgumentOutOfRangeException(nameof(signalCount)); } ThrowIfDisposed(); Debug.Assert(m_event != null); int observedCount; SpinWait spin = new SpinWait(); while (true) { observedCount = m_currentCount; // If the latch is already signaled, we will fail. if (observedCount < signalCount) { throw new InvalidOperationException(Environment.GetResourceString("CountdownEvent_Decrement_BelowZero")); } // This disables the "CS0420: a reference to a volatile field will not be treated as volatile" warning // for this statement. This warning is clearly senseless for Interlocked operations. #pragma warning disable 0420 if (Interlocked.CompareExchange(ref m_currentCount, observedCount - signalCount, observedCount) == observedCount) #pragma warning restore 0420 { break; } // The CAS failed. Spin briefly and try again. spin.SpinOnce(); } // If we were the last to signal, set the event. if (observedCount == signalCount) { m_event.Set(); return true; } Debug.Assert(m_currentCount >= 0, "latch was decremented below zero"); return false; } /// /// Increments the 's current count by one. /// /// The current instance is already /// set. /// is equal to . /// /// The current instance has already been disposed. /// public void AddCount() { AddCount(1); } /// /// Attempts to increment the 's current count by one. /// /// true if the increment succeeded; otherwise, false. If is /// already at zero. this will return false. /// is equal to . /// The current instance has already been /// disposed. public bool TryAddCount() { return TryAddCount(1); } /// /// Increments the 's current count by a specified /// value. /// /// The value by which to increase . /// is less than /// 0. /// The current instance is already /// set. /// is equal to . /// The current instance has already been /// disposed. public void AddCount(int signalCount) { if (!TryAddCount(signalCount)) { throw new InvalidOperationException(Environment.GetResourceString("CountdownEvent_Increment_AlreadyZero")); } } /// /// Attempts to increment the 's current count by a /// specified value. /// /// The value by which to increase . /// true if the increment succeeded; otherwise, false. If is /// already at zero this will return false. /// is less /// than 0. /// The current instance is already /// set. /// is equal to . /// The current instance has already been /// disposed. public bool TryAddCount(int signalCount) { if (signalCount <= 0) { throw new ArgumentOutOfRangeException(nameof(signalCount)); } ThrowIfDisposed(); // Loop around until we successfully increment the count. int observedCount; SpinWait spin = new SpinWait(); while (true) { observedCount = m_currentCount; if (observedCount <= 0) { return false; } else if (observedCount > (Int32.MaxValue - signalCount)) { throw new InvalidOperationException(Environment.GetResourceString("CountdownEvent_Increment_AlreadyMax")); } // This disables the "CS0420: a reference to a volatile field will not be treated as volatile" warning // for this statement. This warning is clearly senseless for Interlocked operations. #pragma warning disable 0420 if (Interlocked.CompareExchange(ref m_currentCount, observedCount + signalCount, observedCount) == observedCount) #pragma warning restore 0420 { break; } // The CAS failed. Spin briefly and try again. spin.SpinOnce(); } return true; } /// /// Resets the to the value of . /// /// /// Unlike most of the members of , Reset is not /// thread-safe and may not be used concurrently with other members of this instance. /// /// The current instance has already been /// disposed.. public void Reset() { Reset(m_initialCount); } /// /// Resets the to a specified value. /// /// The number of signals required to set the . /// /// Unlike most of the members of , Reset is not /// thread-safe and may not be used concurrently with other members of this instance. /// /// is /// less than 0. /// The current instance has alread been disposed. public void Reset(int count) { ThrowIfDisposed(); if (count < 0) { throw new ArgumentOutOfRangeException(nameof(count)); } m_currentCount = count; m_initialCount = count; if (count == 0) { m_event.Set(); } else { m_event.Reset(); } } /// /// Blocks the current thread until the is set. /// /// /// The caller of this method blocks indefinitely until the current instance is set. The caller will /// return immediately if the event is currently in a set state. /// /// The current instance has already been /// disposed. public void Wait() { Wait(Timeout.Infinite, new CancellationToken()); } /// /// Blocks the current thread until the is set, while /// observing a . /// /// The to /// observe. /// /// The caller of this method blocks indefinitely until the current instance is set. The caller will /// return immediately if the event is currently in a set state. If the /// CancellationToken being observed /// is canceled during the wait operation, an /// will be thrown. /// /// has been /// canceled. /// The current instance has already been /// disposed. public void Wait(CancellationToken cancellationToken) { Wait(Timeout.Infinite, cancellationToken); } /// /// Blocks the current thread until the is set, using a /// to measure the time interval. /// /// A that represents the number of /// milliseconds to wait, or a that represents -1 milliseconds to /// wait indefinitely. /// true if the was set; otherwise, /// false. /// is a negative /// number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater /// than . /// The current instance has already been /// disposed. public bool Wait(TimeSpan timeout) { long totalMilliseconds = (long)timeout.TotalMilliseconds; if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue) { throw new ArgumentOutOfRangeException(nameof(timeout)); } return Wait((int)totalMilliseconds, new CancellationToken()); } /// /// Blocks the current thread until the is set, using /// a to measure the time interval, while observing a /// . /// /// A that represents the number of /// milliseconds to wait, or a that represents -1 milliseconds to /// wait indefinitely. /// The to /// observe. /// true if the was set; otherwise, /// false. /// is a negative /// number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater /// than . /// The current instance has already been /// disposed. /// has /// been canceled. public bool Wait(TimeSpan timeout, CancellationToken cancellationToken) { long totalMilliseconds = (long)timeout.TotalMilliseconds; if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue) { throw new ArgumentOutOfRangeException(nameof(timeout)); } return Wait((int)totalMilliseconds, cancellationToken); } /// /// Blocks the current thread until the is set, using a /// 32-bit signed integer to measure the time interval. /// /// The number of milliseconds to wait, or (-1) to wait indefinitely. /// true if the was set; otherwise, /// false. /// is a /// negative number other than -1, which represents an infinite time-out. /// The current instance has already been /// disposed. public bool Wait(int millisecondsTimeout) { return Wait(millisecondsTimeout, new CancellationToken()); } /// /// Blocks the current thread until the is set, using a /// 32-bit signed integer to measure the time interval, while observing a /// . /// /// The number of milliseconds to wait, or (-1) to wait indefinitely. /// The to /// observe. /// true if the was set; otherwise, /// false. /// is a /// negative number other than -1, which represents an infinite time-out. /// The current instance has already been /// disposed. /// has /// been canceled. public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken) { if (millisecondsTimeout < -1) { throw new ArgumentOutOfRangeException(nameof(millisecondsTimeout)); } ThrowIfDisposed(); cancellationToken.ThrowIfCancellationRequested(); bool returnValue = IsSet; // If not completed yet, wait on the event. if (!returnValue) { // ** the actual wait returnValue = m_event.Wait(millisecondsTimeout, cancellationToken); //the Wait will throw OCE itself if the token is canceled. } return returnValue; } // -------------------------------------- // Private methods /// /// Throws an exception if the latch has been disposed. /// private void ThrowIfDisposed() { if (m_disposed) { throw new ObjectDisposedException("CountdownEvent"); } } } }