线程同步 –AutoResetEvent和ManualResetEvent
上一篇介绍了通过lock关键字和Monitor类型进行线程同步,本篇中就介绍一下通过同步句柄进行线程同步。
在Windows系统中,可以使用内核对象进行线程同步,内核对象由系统创建并维护。内核对象为内核所拥有,所以不同进程可以访问同一个内核对象, 如进程、线程、事件、信号量、互斥量等都是内核对象。其中,信号量,互斥体,事件是Windows专门用来进行线程同步的内核对象。
在.NET中,有一个WaitHandle抽象类,这个类型封装了一个Windows内核对象句柄,在C#代码中,我们就可以使用WaitHandle类型(确切的说是子类型)的实例进行线程同步了。下面图中显示了WaitHandle类型的所有子类,下面对各个类型进行介绍。

WaitHandle类型
WaitHandle类型中的SafeWaitHandle属性就是我们前面提到的Windows内核对象句柄,WaitHandle是一个抽象类,不能实例化。

下面看看WaitHandle中常用方法:
- 实例方法:
- WaitOne():阻止当前线程,直到当前 WaitHandle 收到信号
- WaitOne(Int32):阻止当前线程,直到当前 WaitHandle 收到信号,同时使用 32 位带符号整数表示超时时间
- 静态方法:
- WaitAll(WaitHandle[]):等待指定数组中的所有元素都收到信号
- WaitAll(WaitHandle[], Int32):等待指定数组中的所有元素接收信号,同时使用 32 位带符号整数表示超时时间
- WaitAny(WaitHandle[]):等待指定数组中的任一元素收到信号
- WaitAny(WaitHandle[], Int32):等待指定数组中的任意元素接收信号,同时使用 32 位带符号整数表示超时时间
同步事件EventWaitHandle
EventWaitHandle 类允许线程通过发信号互相通信, 通常情况下,一个或多个线程在 EventWaitHandle 上阻止,直到一个未阻止的线程调用 Set 方法,以释放一个或多个被阻止的线程。

在EventWaitHandle类型中,除了父类中的方法,又有自己的特有方法,下面几个是比较常用的:
- 实例方法:
- Set:将事件状态设置为终止状态,允许一个或多个等待线程继续
- Reset:将事件状态设置为非终止状态,导致线程阻止
- 静态方法:
- OpenExisting(String):打开指定名称为同步事件(如果已经存在);通过一个命名的EventWaitHandle我们可以进行进程之间的线程同步,未命名的EventWaitHandle只能进行本进程中的线程同步
EventWaitHandle类型有两个子类AutoResetEvent和ManualResetEvent,这两个子类分别代表了EventWaitHandle类型对事件状态的重置模式。在释放单个等待线程后,用 EventResetMode.AutoReset 标志创建的 EventWaitHandle 在终止时会自动重置; 用 EventResetMode.ManualReset 标志创建的 EventWaitHandle 一直保持终止状态,直到它的 Reset 方法被调用。
通过EventWaitHandle类型的构造函数,我们可以通过参数指定EventResetMode,从而选择事件状态的重置模式。当然,我们也可以直接使用EventWaitHandle的两个子类。
public EventWaitHandle(bool initialState, EventResetMode mode); public enum EventResetMode
{
AutoReset = ,
ManualReset = ,
}
使用AutoResetEvent
AutoResetEvent 允许线程通过发信号互相通信:
- 线程通过调用 AutoResetEvent 上的 WaitOne 来等待信号。 如果 AutoResetEvent 为非终止状态,则线程会被阻止,并等待当前控制资源的线程通过调用 Set 来通知资源可用。
- 线程通过调用 Set 向 AutoResetEvent 发信号以释放等待线程。 AutoResetEvent 将保持终止状态,直到一个正在等待的线程被释放,然后自动返回非终止状态。 如果没有任何线程在等待,则状态将无限期地保持为终止状态。
可以通过将一个布尔值传递给构造函数来控制 AutoResetEvent 的初始状态:如果初始状态为终止状态,则为 true;否则为 false。
下面看一个例子:
namespace AutoResetEventTest
{
class Program
{
//AutoResetEvent实例初始为非终止状态
private static AutoResetEvent autoResetEvent = new AutoResetEvent(false); static void Main(string[] args)
{
new Thread(() =>
{
while (true)
{
//调用WaitOne来等待信号,并设置超时时间为5秒
bool status = autoResetEvent.WaitOne();
if (status)
{
Console.WriteLine("ThreadOne get the signal");
}
else
{
Console.WriteLine("ThreadOne timeout(5 seconds) waiting for signal");
break;
}
}
Console.WriteLine("ThreadOne Exit"); }).Start(); new Thread(() =>
{
while (true)
{
//调用WaitOne来等待信号,并设置超时时间为5秒
bool status = autoResetEvent.WaitOne();
if (status)
{
Console.WriteLine("ThreadTwo get the signal");
}
else
{
Console.WriteLine("ThreadTwo timeout(5 seconds) waiting for signal");
break;
}
}
Console.WriteLine("ThreadTwo Exit"); }).Start(); Random ran = new Random();
for (int i = ; i < ; i++)
{ Thread.Sleep(ran.Next(, ));
//通过Set向 AutoResetEvent 发信号以释放等待线程
Console.WriteLine("Main thread send the signal");
autoResetEvent.Set();
} Console.Read();
}
}
}
代码的输出为下,通过结果也可以验证,每次调用Set方法之后,AutoResetEvent 将保持终止状态,直到一个正在等待的线程被释放,然后自动返回非终止状态。

使用ManualResetEvent
像AutoResetEvent一样,ManualResetEvent 也是线程通过发信号互相通信:
- 线程通过调用 ManualResetEvent上的 WaitOne 来等待信号。 如果 ManualResetEvent为非终止状态,则线程会被阻止,并等待当前控制资源的线程通过调用 Set 来通知资源可用。
- 线程通过调用 Set 向 ManualResetEvent发信号以释放等待线程。 与AutoResetEvent不同的是,ManualResetEvent将一直保持终止状态,并释放所有等待线程,直到有线程通过Reset将 ManualResetEvent 置于非终止状态。
- 线程通过调用 Reset以将 ManualResetEvent 置于非终止状态。
可以通过将布尔值传递给构造函数来控制 ManualResetEvent 的初始状态,如果初始状态处于终止状态,为 true;否则为 false。
看一个例子:
namespace ManualResetEventTest
{
class Program
{
//ManualResetEvent实例初始为非终止状态
private static ManualResetEvent manualResetEvent = new ManualResetEvent(false); static void Main(string[] args)
{
new Thread(() =>
{
//调用WaitOne来等待信号
manualResetEvent.WaitOne();
Console.WriteLine("Thread get the signal - the first time"); Thread.Sleep();
manualResetEvent.WaitOne();
Console.WriteLine("Thread get the signal - the second time"); //调用Reset来以将 ManualResetEvent 置于非终止状态
Console.WriteLine("Child thread reset ManualResetEvent to non-signaled");
manualResetEvent.Reset(); manualResetEvent.WaitOne();
Console.WriteLine("Thread get the signal - the third time"); Console.WriteLine("Child thread reset ManualResetEvent to non-signaled");
manualResetEvent.Reset(); //调用WaitOne来等待信号,并设置超时时间为3秒
manualResetEvent.WaitOne();
Console.WriteLine("timeout while waiting for signal");
}).Start(); //通过Set向 ManualResetEvent 发信号以释放等待线程
Console.WriteLine("Main thread set ManualResetEvent to signaled");
manualResetEvent.Set();
Thread.Sleep();
Console.WriteLine("Main thread set ManualResetEvent to signaled");
manualResetEvent.Set(); Console.Read();
}
}
}
代码的输出如下,通过结果验证了,每次调用Set都会将ManualResetEvent设置为终止状态,并释放所有等待线程。只有手动调用 Reset才能将 ManualResetEvent 置于非终止状态。

实现进程间线程同步
前一篇文章中介绍的lock和Monitor只能进行同一个进程中的线程同步。
但是,由于同步事件EventWaitHandle是基于内核事件的,所以说,它可以实现进程之间的线程同步。
基于前面AutoResetEvent的例子稍作修改:
class Program
{ private static EventWaitHandle eventWaitHandle; private static bool newEventWaitHandleObj = true; static void Main(string[] args)
{
string EventWaitHandleName = "EventWaitHandleTest";
try
{
//尝试打开已有的同步事件
eventWaitHandle = EventWaitHandle.OpenExisting("EventWaitHandleTest");
newEventWaitHandleObj = false;
}
catch (WaitHandleCannotBeOpenedException e)
{
Console.WriteLine("EventWaitHandle named {0} is not exist, error message: {1}", EventWaitHandleName, e.Message);
//实例化同步事件,初始为非终止状态,设置为自动重置模式
eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, "EventWaitHandleTest");
Console.WriteLine("Create EventWaitHandle {0}", EventWaitHandleName);
newEventWaitHandleObj = true;
} new Thread(() =>
{
while (true)
{
//调用WaitOne来等待信号,并设置超时时间为5秒
bool status = eventWaitHandle.WaitOne();
if (status)
{
Console.WriteLine("ThreadOne get the signal");
}
else
{
Console.WriteLine("ThreadOne timeout(5 seconds) waiting for signal");
break;
}
}
Console.WriteLine("ThreadOne Exit"); }).Start(); new Thread(() =>
{
while (true)
{
//调用WaitOne来等待信号,并设置超时时间为5秒
bool status = eventWaitHandle.WaitOne();
if (status)
{
Console.WriteLine("ThreadTwo get the signal");
}
else
{
Console.WriteLine("ThreadTwo timeout(5 seconds) waiting for signal");
break;
}
}
Console.WriteLine("ThreadTwo Exit"); }).Start(); if (newEventWaitHandleObj)
{
Random ran = new Random();
for (int i = ; i < ; i++)
{ Thread.Sleep(ran.Next(, ));
//通过Set向 AutoResetEvent 发信号以释放等待线程
Console.WriteLine("Main thread send the signal");
eventWaitHandle.Set();
}
} Console.Read();
}
}
代码的输出为下,代码中通过OpenExisting方法尝试打开已存在的同步事件句柄,如果失败,就创建一个EventWaitHandle实例。
至于后面部分代码的工作原理,跟AutoResetEvent的例子完全一样。

接下来,我们找到工程生成的exe文件,然后同时启动两次exe文件,可以看到如下输出,后面启动的进程能够打开前面进程创建的同步事件句柄。通过这种方式,就可以实现进程之间的线程同步。

总结
本文介绍了WaitHandle类型,以及该类型的子类型EventWaitHandle,并且介绍了如何通过AutoResetEvent和ManualResetEvent进行线程同步。
AutoResetEvent和ManualResetEvent的区别:
- AutoResetEvent.WaitOne()每次只允许一个线程进入,当某个线程得到信号后,AutoResetEvent会自动又将信号置为非终止状态,则其他调用WaitOne的线程只有继续等待,也就是说AutoResetEvent一次只唤醒一个线程
- ManualResetEvent则可以唤醒多个线程,因为当某个线程调用了ManualResetEvent.Set()方法后,其他调用WaitOne的线程获得信号得以继续执行,而ManualResetEvent不会自动将信号置为非终止状态。也就是说,除非手工调用了ManualResetEvent.Reset()方法,则ManualResetEvent将一直保持有信号状态,ManualResetEvent也就可以同时唤醒多个线程继续执行。
下一篇将继续介绍互斥体Mutex和信号量Semaphore的使用。
线程同步 –AutoResetEvent和ManualResetEvent的更多相关文章
- 线程同步(AutoResetEvent与ManualResetEvent)
前言 在我们编写多线程程序时,会遇到这样一个问题:在一个线程处理的过程中,需要等待另一个线程处理的结果才能继续往下执行.比如:有两个线程,一个用来接收Socket数据,另一个用来处理Socket数据, ...
- C# 应用 - 多线程 6) 处理同步数据之手动同步 AutoResetEvent 和 ManualResetEvent
1. 类的关系 AutoResetEvent 和 ManualResetEvent 都继承自 System.Threading.EventWaitHandle 类(EventWaitHandle 继承 ...
- 线程中AutoResetEvent与ManualResetEvent的区别
线程之间的通信是通过发信号来进行沟通的.. ManualResetEvent发送Set信后后,需要手动Reset恢复初始状态.而对于AutoResetEvent来说,当发送完毕Set信号后,会自动Re ...
- 多线程同步AutoResetEvent 和ManualResetEvent
- 重新想象 Windows 8 Store Apps (47) - 多线程之线程同步: Semaphore, CountdownEvent, Barrier, ManualResetEvent, AutoResetEvent
[源码下载] 重新想象 Windows 8 Store Apps (47) - 多线程之线程同步: Semaphore, CountdownEvent, Barrier, ManualResetEve ...
- 基元线程同步构造 AutoResetEvent和ManualResetEvent 线程同步
在.Net多线程编程中,AutoResetEvent和ManualResetEvent这两个类经常用到, 他们的用法很类似,但也有区别.ManualResetEvent和AutoResetEvent都 ...
- C#多线程同步事件及等待句柄AutoResetEvent 和 ManualResetEvent
最近捣鼓了一下多线程的同步问题,发现其实C#关于多线程同步事件处理还是很灵活,这里主要写一下,自己测试的一些代码,涉及到了AutoResetEvent 和 ManualResetEvent,当然还有也 ...
- C# 使用ManualResetEvent 进行线程同步
上一篇我们介绍了AutoResetEvent,这一篇我们来看下ManualResetEvent ,顾名思义ManualResetEvent 为手动重置事件. AutoResetEvent和Manua ...
- C# 线程间互相通信 AutoResetEvent和ManualResetEvent
C#线程间互相通信主要用到两个类:AutoResetEvent和ManualResetEvent. 一.AutoResetEvent AutoResetEvent 允许线程通过发信号互相通信,线程通过 ...
随机推荐
- Ubuntu16.04怎么将桌面左侧的启动器移动到屏幕底部
与其他 Linux 发行版不同,Ubuntu 多年来一直使用 Unity 做桌面环境,该环境的最突出特点就是桌面左侧有一个启动器栏(Launcher).从 16.04 版本开始,Ubuntu 提供了一 ...
- JPA和Spring-Data-JPA简介
什么是JPA JPA(Java Persistence API)是Sun官方提出的Java持久化规范.它为Java开发人员提供了一种对象/关联映射工具来管理Java应用中的关系数据.它的出现主要是为了 ...
- Spring Cloud Eureka 注册中心 服务消费者 服务提供者之间的关系以及高可用之间的联系
注册中心:提供服务的注册与查询(发现) 服务提供者:服务的提供方,提供服务的一方. 服务消费者:服务的消费方,使用服务的一方. 我们没有注册中心,服务提供者与服务消费者同样可以调用,通过spring中 ...
- [maven] 项目不同环境自动打包
应用背景 项目需要发布到本地环境,测试环境和生产环境甚至不同的生产环境上.这时候配置文件的一些参数需要被频繁的修改来修改去.为了解决这样的繁琐工作,就得使用maven profile特性. 步骤 1. ...
- Selenium常用操作汇总二——iframe的处理
有时候我们在定位一个页面元素的时候发现一直定位不了,反复检查自己写的定位器没有任何问题,代码也没有任何问题.这时你就要看一下这个页面元素是否在一个iframe中,这可能就是找不到的原因之一.如果你在一 ...
- 第三百八十三节,Django+Xadmin打造上线标准的在线教育平台—第三方模块django-simple-captcha验证码
第三百八十三节,Django+Xadmin打造上线标准的在线教育平台—第三方模块django-simple-captcha验证码 下载地址:https://github.com/mbi/django- ...
- Linux 安装 MongoDB数据库
1下载: wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-3.0.7.tgz (下载较慢) 2.安装: mv mongodb-li ...
- e821. 设置JScrollPane滚动栏
A scroll bar in a scroll pane can be set to appear only as needed, always appear, or never appear. B ...
- C语言中 Float 数据结构的存储计算
1.了解float存储结构 float存储结构请看另一篇文章http://blog.csdn.net/whzhaochao/article/details/12885875 2.float最大值 fl ...
- Zookeeper系统设计的优点
转自:Zookeeper系统设计的优点.http://webcache.googleusercontent.com/search?q=cache:s6fr40t_5ncJ:www.chaozh.com ...