C# CountdownEvent實現

關於CountdownEvent網上的介紹比較少,由於它是實現和使用都很簡單,先看看網上的一些評論吧:函數

CountDownEvent調用成員函數Wait()將阻塞,直至成員函數Signal() 被調用達特定的次數,這時CountDownEvent稱做就緒態,對於處於就緒態的CountDownEvent,調用Wait()函數將不會再阻塞,只有手動調用Reset()函數後,調用Wait()函數將再次阻塞。CountDownEvent能夠經過TryAddCount()和AddCount()函數來增長函數Signal() 需被調用的次數,但只有當CountDownEvent處於未就緒態時纔會成功。不然根據調用函數的不一樣,將有可能拋出異常
當有新的須要同步的任務產生時,就調用AddCount增長它的計數,當有任務到達同步點是,就調用Signal函數減少它的計數,當CountdownEvent的計數爲零時,就表示全部須要同步的任務已經完成,能夠開始下一步任務了。spa

咱們來看看CountdownEvent的實現:線程

public class CountdownEvent : IDisposable
{
    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.
    public CountdownEvent(int initialCount)
    {
        if (initialCount < 0)
        {
            throw new ArgumentOutOfRangeException("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(); }
    }
    public int CurrentCount
    {
        get 
        {
            int observedCount = m_currentCount;
            return observedCount < 0 ? 0 : observedCount;
        }
    }
    public bool IsSet
    {
        get
        {
            return (m_currentCount <= 0);
        }
    }
    
    //<returns>true if the signal caused the count to reach zero and the event was set; otherwise, false.
    public bool Signal()
    {
        ThrowIfDisposed();
        Contract.Assert(m_event != null);

        if (m_currentCount <= 0)
        {
            throw new InvalidOperationException(Environment.GetResourceString("CountdownEvent_Decrement_BelowZero"));
        }
        int newCount = Interlocked.Decrement(ref m_currentCount);
        if (newCount == 0)
        {
            m_event.Set();
            return true;
        }
        else if (newCount < 0)
        {
            throw new InvalidOperationException(Environment.GetResourceString("CountdownEvent_Decrement_BelowZero"));
        }
        return false;
    }
    
    //<returns>true if the signals caused the count to reach zero and the event was set; otherwise, false.
    public bool Signal(int signalCount)
    {
        if (signalCount <= 0)
        {
            throw new ArgumentOutOfRangeException("signalCount");
        }

        ThrowIfDisposed();
        Contract.Assert(m_event != null);

        int observedCount;
        SpinWait spin = new SpinWait();
        while (true)
        {
           observedCount = m_currentCount;
            if (observedCount < signalCount)
            {
                throw new InvalidOperationException(Environment.GetResourceString("CountdownEvent_Decrement_BelowZero"));
            }
            if (Interlocked.CompareExchange(ref m_currentCount, observedCount - signalCount, observedCount) == observedCount)
            {
                break;
            }
            spin.SpinOnce();
        }
        if (observedCount == signalCount)
        {
            m_event.Set();
            return true;
        }
        Contract.Assert(m_currentCount >= 0, "latch was decremented below zero");
        return false;
    }

    public void AddCount(int signalCount)
    {
        if (!TryAddCount(signalCount))
        {
            throw new InvalidOperationException(Environment.GetResourceString("CountdownEvent_Increment_AlreadyZero"));
        }
    }
    //<returns> true if the increment succeeded; otherwise, false
    public bool TryAddCount(int signalCount)
    {
        if (signalCount <= 0)
        {
            throw new ArgumentOutOfRangeException("signalCount");
        }

        ThrowIfDisposed();
        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"));
            }

            if (Interlocked.CompareExchange(ref m_currentCount, observedCount + signalCount, observedCount) == observedCount)
            {
                break;
            }
            spin.SpinOnce();
        }
        return true;
    }
    
    public void Reset()
    {
        Reset(m_initialCount);
    }
    public void Reset(int count)
    {
        ThrowIfDisposed();
        if (count < 0)
        {
            throw new ArgumentOutOfRangeException("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
    public void Wait()
    {
        Wait(Timeout.Infinite, new CancellationToken());
    }
    public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
    {
        if (millisecondsTimeout < -1)
        {
            throw new ArgumentOutOfRangeException("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;
    }   
}

代碼很是簡單,CountdownEvent內部是實現仍是依賴於ManualResetEventSlim實例,int initialCount參數爲0是就調用ManualResetEventSlim的set方法,這樣Wait的對象就不被阻塞了。IsSet是一個主要屬性【return (m_currentCount <= 0)】,Signal()和Signal(int signalCount)方法就是減小m_currentCount的,主要採用原子操做【Interlocked.Decrement(ref m_currentCount)】 和【Interlocked.CompareExchange(ref m_currentCount, observedCount - signalCount, observedCount) == observedCount】,若是減小後m_currentCount==0 就調用set方法,爲Wait的線程放行;注意這裏面有使用SpinWait的自旋,那麼AddCount、TryAddCount和Signal方法相反,主要是增長m_currentCount,實現方式同樣,採用原子操做+自旋;Reset還原爲初始值,Reset(int count)還原爲制定的值,Wait方法主要看是不是IsSet【是則是調用了Set方法的】,則直接返回【當前m_currentCount==0】,否者就調用ManualResetEventSlim的Wai方法code

相關文章
相關標籤/搜索