• 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里面

  • 相关阅读:
    初学springMVC的拦截器
    jquery_EasyUI使用细节注意
    引进js文件运行在浏览器上调试报文件找不到
    Dreamweaver安装须知
    请求路径@PathVariable与请求参数@RequestParam的区别
    接口是否继承接口
    存储过程的学习
    webservice面试题
    哪些异常是RuntimeException?Sql异常属于RuntimeException吗?Spring下SQL异常事务回滚
    Trensient的使用介绍
  • 原文地址:https://www.cnblogs.com/majiang/p/7891894.html
Copyright © 2020-2023  润新知