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; //
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 < -)
{
throw new ArgumentOutOfRangeException("millisecondsTimeout");
}
if (!IsSet)
{
if (millisecondsTimeout == )
{
// 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 = ;
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 = ;
int HOW_MANY_YIELD_EVERY_SLEEP_0 = ;
int HOW_MANY_YIELD_EVERY_SLEEP_1 = ; int spinCount = SpinCount;
for (int i = ; 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 <= )
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里面

C# ManualResetEventSlim 实现的更多相关文章

  1. C#并行编程 (Barrier,CountdownEvent,ManualResetEventSlim,SemaphoreSlim,SpinLock,SpinWait )

    背景 有时候必须访问变量.实例.方法.属性或者结构体,而这些并没有准备好用于并发访问,或者有时候需要执行部分代码,而这些代码必须单独运行,这是不得不通过将任务分解的方式让它们独立运行. 当任务和线程要 ...

  2. .net core WebApi Interlocked配合ManualResetEventSlim实现并发同步

    由于项目有某种需求,在WebApi中,有大量的请求需要操作相同的数据,因此需要用到并发同步机制去操作共享的数据. 本次配合使用Interlocked和ManualResetEventSlim来实现并发 ...

  3. 《C#多线程编程实战》2.6 ManualResetEventSlim

    这个比较好理解的. 正如书上所言,如同一直在打开的大门的屋子,谁要进去,谁就自己的关门,出来的时候在开开. 常用的方法 有三个: Set()  //设置为有信号,也就是让等待的线程不用继续等待,唤醒等 ...

  4. 一次 .NET Core 中玩锁的经历:ManualResetEventSlim, Semaphore 与 SemaphoreSlim

    最近同事对  .net core memcached 缓存客户端 EnyimMemcachedCore 进行了高并发下的压力测试,发现在 linux 上高并发下使用 async 异步方法读取缓存数据会 ...

  5. 线程之间灵活传递信号(ManualResetEventSlim )

    当主程序启动时,首先创建ManualResetEventSlim 类的一个实例.然后启动三个线程,等待事件信号通知它们继续执行. /// <summary> /// 创建 ManualRe ...

  6. C#多线程之线程同步篇3

    在上一篇C#多线程之线程同步篇2中,我们主要学习了AutoResetEvent构造.ManualResetEventSlim构造和CountdownEvent构造,在这一篇中,我们将学习Barrier ...

  7. C#多线程之线程同步篇2

    在上一篇C#多线程之线程同步篇1中,我们主要学习了执行基本的原子操作.使用Mutex构造以及SemaphoreSlim构造,在这一篇中我们主要学习如何使用AutoResetEvent构造.Manual ...

  8. C#多线程之线程同步篇1

    在多线程(线程同步)中,我们将学习多线程中操作共享资源的技术,学习到的知识点如下所示: 执行基本的原子操作 使用Mutex构造 使用SemaphoreSlim构造 使用AutoResetEvent构造 ...

  9. C# 线程同步的三类情景

    C# 已经提供了我们几种非常好用的类库如 BackgroundWorker.Thread.Task等,借助它们,我们就能够分分钟编写出一个多线程的应用程序. 比如这样一个需求:有一个 Winform ...

随机推荐

  1. Codeforces 1140G Double Tree 倍增 + dp

    刚开始, 我以为两个点肯定是通过树上最短路径过去的, 无非是在两棵树之间来回切换, 这个可以用倍增 + dp 去维护它. 但是后来又发现, 它可以不通过树上最短路径过去, 我们考虑这样一种情况, 起点 ...

  2. 关于window.open窗口的resize事件

    jQuery 事件 - resize() 方法 当调整浏览器窗口的大小时,发生 resize 事件. resize() 方法触发 resize 事件,或规定当发生 resize 事件时运行的函数. & ...

  3. 040 Http与RPC

    一直在说,但是没有系统的文档理解过. 1.本质 目前有很多Java的RPC框架,有基于Json的,有基于XML,也有基于二进制对象的. 他们最本质的区别,就是RPC主要是基于TCP/IP协议的,而HT ...

  4. day 49-css补充(终结)[浮动和定位]

    老师的笔记: 前情回顾: day49 混乱即阶梯. 1. 前情回顾 HTML HTTP和HTML 文档机构 <!Doctype html> HTML head <meta> & ...

  5. maven聚合工程无法install

    对于maven聚合工程,有时候执行maven命令进行mvn clean install时会出一些莫名奇妙的错误: 一直报告找不到符号,仔细看了项目源代码也不知道是什么原因.首先确保项目所在路径为英文路 ...

  6. Codeforces 870C Maximum splitting (贪心+找规律)

    <题目链接> 题目大意: 给定数字n,让你将其分成合数相加的形式,问你最多能够将其分成几个合数相加. 解题分析: 因为要将其分成合数相加的个数最多,所以自然是尽可能地将其分成尽可能小的合数 ...

  7. Adobe Premiere Pro生成峰值文件假死

    一.正文 使用Adobe的Premiere Pro CC进行视频剪辑制作的时候,有的时候在右下角总会出现一个“自动生成峰值文件”的提示符,并跟随一个进度条: 大部分时候,这并不会引起什么问题.虽然我也 ...

  8. A - Character Encoding HDU - 6397 - 方程整数解-容斥原理

    A - Character Encoding HDU - 6397 思路 : 隔板法就是在n个元素间的(n-1)个空中插入k-1个板,可以把n个元素分成k组的方法 普通隔板法 求方程 x+y+z=10 ...

  9. UVA 814 The Letter Carrier's Rounds

    大致翻译: 对于电子邮件应用程序,您需要描述发生在成对mta之间的基于smtp的通信.发送方 的用户代理向发送消息传输代理(MTA)提供格式化的消息.发送MTA使用SimpleMail 传输协议(SM ...

  10. sql语句练习50题(Mysql版)

    表名和字段–1.学生表Student(s_id,s_name,s_birth,s_sex) –学生编号,学生姓名, 出生年月,学生性别–2.课程表Course(c_id,c_name,t_id) – ...