zoukankan      html  css  js  c++  java
  • C# ManualResetEventSlim 实现

    ManualResetEventSlim通过封装 ManualResetEvent提供了自旋等待和内核等待的组合。如果需要跨进程或者跨AppDomain的同步,那么就必须使用ManualResetEvent,而不能使用ManualResetEventSlim。那么首先我们看看 ManualResetEvent和AutoResetEvent的使用特点,只有搞清楚了ManualResetEvent才可能明白ManualResetEventSlim的好处。

    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而导致所有线程都得不到执行

    我们来看看ManualResetEventSlim的实现:

    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方法里面增加了自旋

    这里面的using (cancellationToken.InternalRegisterWithoutEC(s_cancellationTokenCallback, this))也是非常重要Monitor.Wait方法只是把线程放到等待队列,调用ManualResetEvent的Set方法会调用   Monitor.PulseAll(m_lock);,但是在调用ManualResetEvent的wait方法,里面调用了cancellationToken.ThrowIfCancellationRequested()该如何处理,这个时候的lock锁没有释放,需要调用 Monitor.PulseAll方法,所以该方法被方放到CancellationTokenCallback里面

  • 相关阅读:
    Best Time to Buy and Sell Stock III
    Valid Palindrome
    Longest Substring Without Repeating Characters
    Copy List with Random Pointer
    Add Two Numbers
    Recover Binary Search Tree
    Anagrams
    ZigZag Conversion
    Merge k Sorted Lists
    Distinct Subsequences
  • 原文地址:https://www.cnblogs.com/majiang/p/7891894.html
Copyright © 2011-2022 走看看