C# ManualResetEventSlim 實現

ManualResetEventSlim經過封裝 ManualResetEvent提供了自旋等待和內核等待的組合。若是須要跨進程或者跨AppDomain的同步,那麼就必須使用ManualResetEvent,而不能使用ManualResetEventSlim。那麼首先咱們看看 ManualResetEvent和AutoResetEvent的使用特色,只有搞清楚了ManualResetEvent纔可能明白ManualResetEventSlim的好處。ide

ManualResetEvent和AutoResetEvent的共同點:
1)Set方法將事件狀態設置爲終止狀態,容許一個或多個等待線程繼續;Reset方法將事件狀態設置爲非終止狀態,致使線程阻止;WaitOne阻止當前線程,直到當前線程的WaitHandler收到事件信號
2)能夠經過構造函數的參數值來決定其初始狀態,若爲true則事件爲終止狀態從而使線程爲非阻塞狀態,爲false則線程爲阻塞狀態
3)若是某個線程調用WaitOne方法,則當事件狀態爲終止狀態時,該線程會獲得信號,繼續向下執行函數

ManualResetEvent和AutoResetEvent的不一樣點:
1)AutoResetEvent.WaitOne()每次只容許一個線程進入,當某個線程獲得信號後,AutoResetEvent會自動又將信號置爲不發送狀態,則其餘調用WaitOne的線程只有繼續等待,也就是說AutoResetEvent一次只喚醒一個線程
2)ManualResetEvent則能夠喚醒多個線程,由於當某個線程調用了ManualResetEvent.Set()方法後,其餘調用WaitOne的線程得到信號得以繼續執行,而ManualResetEvent不會自動將信號置爲不發送
3)也就是說,除非手工調用了ManualResetEvent.Reset()方法,則ManualResetEvent將一直保持有信號狀態,ManualResetEvent也就能夠同時喚醒多個線程繼續執行

AutoResetEvent myResetEvent = new AutoResetEvent(false)
構造方法的參數設置成false後,表示建立一個沒有被set的AutoResetEvent,這就致使全部持有這個AutoResetEvent的線程都會在WaitOne()處掛起, 若是將參數設置成true,表示建立一個被set的AutoResetEvent,持有這個AutoResetEvent的線程們會競爭這個Event ,此時在其餘條件知足的狀況下,至少會有一個線程獲得執行,而不是因得不到Event而致使全部線程都得不到執行

ManualResetEvent myResetEvent = new ManualResetEvent(false)
構造方法的參數設置成false後,表示建立一個沒有被set的ManualResetEvent,這就致使全部持有這個ManualResetEvent的線程都會在WaitOne()處掛起 ,若是將參數設置成true,表示建立一個被set的ManualResetEvent ,持有這個ManualResetEvent的線程們在其餘條件知足的狀況下會同時獲得執行(注意,是同時獲得執行);而不是因得不到Event而致使全部線程都得不到執行
oop

咱們來看看ManualResetEventSlim的實現:ui

public class ManualResetEventSlim : IDisposable
{
    private volatile object m_lock;
    // A lock used for waiting and pulsing. Lazily initialized via EnsureLockObjectCreated()
    private volatile ManualResetEvent m_eventObj; // A true Win32 event used for waiting.
    private const int DEFAULT_SPIN_MP = SpinWait.YIELD_THRESHOLD; //10
    public ManualResetEventSlim(bool initialState)
    {
        // Specify the defualt spin count, and use default spin if we're
        // on a multi-processor machine. Otherwise, we won't.
        Initialize(initialState, DEFAULT_SPIN_MP);
    }
    public void Set()
    {
        Set(false);
    }
    private void Set(bool duringCancellation)
    {
        // We need to ensure that IsSet=true does not get reordered past the read of m_eventObj
        // This would be a legal movement according to the .NET memory model. 
        // The code is safe as IsSet involves an Interlocked.CompareExchange which provides a full memory barrier.
        IsSet = true;

        // If there are waiting threads, we need to pulse them.
        if (Waiters > 0)
        {
            Contract.Assert(m_lock != null); //if waiters>0, then m_lock has already been created.
            lock (m_lock)
            {
               Monitor.PulseAll(m_lock);
            }
        }
       ManualResetEvent eventObj = m_eventObj;
        if (eventObj != null && !duringCancellation)
        {        
            lock (eventObj)
            {
                if (m_eventObj != null)
                {
                    // If somebody is waiting, we must set the event.
 m_eventObj.Set();
                }
            }
        }
    }
   public void Reset()
    {
        ThrowIfDisposed();
        // If there's an event, reset it.
        if (m_eventObj != null)
        {
           m_eventObj.Reset();
        }
        IsSet = false;
    }
    public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
    {
        ThrowIfDisposed();
        cancellationToken.ThrowIfCancellationRequested(); // 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.
            uint startTime = 0;
            bool bNeedTimeoutAdjustment = false;
            int realMillisecondsTimeout = millisecondsTimeout; //this will be adjusted if necessary.

            if (millisecondsTimeout != Timeout.Infinite)
            {
                startTime = TimeoutHelper.GetTime();
                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;

            int spinCount = SpinCount;
            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)
                    {
                        Thread.Yield();
                    }
                    else
                    {
                        Thread.SpinWait(PlatformHelper.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
                {
                    Thread.Yield();
                }
                if (i >= 100 && i % 10 == 0) // check the cancellation token if the user passed a very large spin count
 cancellationToken.ThrowIfCancellationRequested();
            }

            // Now enter the lock and wait.
            EnsureLockObjectCreated();

            // We must register and deregister the token outside of the lock, to avoid deadlocks.
            using (cancellationToken.InternalRegisterWithoutEC(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.ThrowIfCancellationRequested();

                        //update timeout (delays in wait commencement are due to spinning and/or spurious wakeups from other waits being canceled)
                        if (bNeedTimeoutAdjustment)
                        {
                            realMillisecondsTimeout = TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout);
                            if (realMillisecondsTimeout <= 0)
                                return false;
                        }        
                      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;
                        }
                    }
                }
            }
        } // automatically disposes (and deregisters) the callback 

        return true; //done. The wait was satisfied.
    }
    private void EnsureLockObjectCreated()
    {
        Contract.Ensures(m_lock != null);
        if (m_lock != null)
            return;
        object newObj = new object();
        Interlocked.CompareExchange(ref m_lock, newObj, null); // failure is benign.. someone else won the ----.
    }
    private static Action<object> s_cancellationTokenCallback = new Action<object>(CancellationTokenCallback);
    private static void CancellationTokenCallback(object obj)
    {
        ManualResetEventSlim mre = obj as ManualResetEventSlim;
        Contract.Assert(mre != null, "Expected a ManualResetEventSlim");
        Contract.Assert(mre.m_lock != null); //the lock should have been created before this callback is registered for use.
        lock (mre.m_lock)
        {
       Monitor.PulseAll(mre.m_lock); // awaken all waiters
        }
    }    
}
 public sealed class ManualResetEvent : EventWaitHandle
{        
    public ManualResetEvent(bool initialState) : base(initialState,EventResetMode.ManualReset){}
}

其中的Reset方法最簡單就是調用 ManualResetEvent的Reset方法,Set方法也是調用ManualResetEvent的Set方法,只是在Set方法前須要把等待隊列的線程轉換爲就緒狀態【lock (m_lock){Monitor.PulseAll(m_lock);}】,ManualResetEventSlim 與ManualResetEvent的區別主要是Wait方法裏面增長了自旋this

這裏面的using (cancellationToken.InternalRegisterWithoutEC(s_cancellationTokenCallback, this))也是很是重要Monitor.Wait方法只是把線程放到等待隊列,調用ManualResetEvent的Set方法會調用   Monitor.PulseAll(m_lock);,可是在調用ManualResetEvent的wait方法,裏面調用了cancellationToken.ThrowIfCancellationRequested()該如何處理,這個時候的lock鎖沒有釋放,須要調用 Monitor.PulseAll方法,因此該方法被方放到CancellationTokenCallback裏面spa

相關文章
相關標籤/搜索