博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C# ManualResetEventSlim 实现
阅读量:6976 次
发布时间:2019-06-27

本文共 9186 字,大约阅读时间需要 30 分钟。

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 s_cancellationTokenCallback = new Action(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里面

转载地址:http://xxesl.baihongyu.com/

你可能感兴趣的文章
Gulp 前端sass 编译浏览器重载
查看>>
openssl加密、解密
查看>>
系统日志相关操作总结
查看>>
java基础第十七天_QQ案例
查看>>
CoreOS实践指南(五):分布式数据存储Etcd(上)
查看>>
系统学习redis之一——基础概念
查看>>
form属性method="get"/"post"的两种方式对比
查看>>
老李推荐:第8章2节《MonkeyRunner源码剖析》MonkeyRunner启动运行过程-解析处理命令行参数 3...
查看>>
nload命令
查看>>
SpringMVC基础框架搭建
查看>>
在线帮助你修改图片背景的工具 - Clipping Magic
查看>>
zuul转发的一些常见异常
查看>>
聊聊spring cloud gateway的GatewayFilter
查看>>
监控zabbix3.4 +granfana5.1.3
查看>>
Linux 第六周学习笔记(1) kickstart脚本 pxe网络安装服务
查看>>
MySQL数据类型
查看>>
vim编辑模式
查看>>
Linux常用命令——sudo
查看>>
iptables-ipset模块
查看>>
Java Web分页设计实现,基于pager-taglib+Filter
查看>>