# [C# 线程处理系列]专题五:线程同步——事件构造
引言:
其实这部分内容应该是属于专题四,因为这篇也是讲关于线程同步的,但是由于考虑到用户的阅读习惯问题,因为文章太长了,很多人不是很愿意看包括我也是这样的,同时也有和我说可以把代码弄成折叠的,这样就不会太长的,但是我觉得这样也不怎么便于阅读,因为我看别人的博客的时候,看到有代码是折叠起来的时候很多时候不愿意去点,并且点一下之后同样拉长文章的,然后就看到右边的滚动条变小了,本以为快看完了(意思快学到知识了),一看滚动条后发现还有好长的内容很看, 所以就会给人一种不舒服的感觉吧(如果有和我一样的人的话,你肯定懂的是什么感觉的)。所以我把线程同步放到两篇文章里面来说,其实放到两篇文章里面也有一定原因的, 前面讲的线程同步主要是用户模式的(CLR Via C# 一书中是这么定义的,书中说到线程同步分两种:一、用户模式构造 二、内核模式构造,第一次看的时候不是很理解两个名词是什么意思的,我一般理解东西是采用把东西拆分来理解,理解拆分的各个部分后再合起来理解内容的,现在我对着两个的理解是——用户模式构造:对于内核模式构造(指的的是构造操作系内核对象),我们使用类(.net Framework中的类,如 AutoResetEvent, Semaphore类)的方法来实现线程同步,其实内部是调用操作系统的内核对象来实现的线程同步,此时就会导致线程从托管代码到为内核代码,然而用户模式构造,没有调用操作系统内核对象,线程只是在用户的托管代码上执行的),对于用户模式构造和内核模式的构造只是我自己的理解的, 如果有更好的理解方式可以留言告诉下我, 这样我们可以一起讨论和学习了。
目录:
一、WaitHandle基类介绍
二、事件(Event)类实现线程同步
三、信号量(Semapyore)类实现线程同步
四、互斥体(Mutex)实现线程同步
**一、WaitHandle基类介绍**
**System.Threading**命名空间中提供了一个**WaitHandle** 的抽象基类,此类就是包装了一个Windows内核对象的句柄(句柄可以理解为标示了对象实例的一个数字,具体大家可以查看资料深入理解下的,在这里只是提出理解句柄也是很重要的),在.net Framework中提供了从WaitHandle类中派生的类(我正是用这些派生类在我们的代码中实现线程同步的)。它们的一个继承关系为
**WaitHandle**
**EventWaitHandle**
**AutoResetEvent**
**ManualResetEvent**
**Semaphore**
**Mutex**
当我们在使用 **AutoResetEvent**,**ManualResetEvent,****Semaphore,****Mutex**这些类的时候,用构造函数来实例化这些类的对象时,其内部都调用了Win32 CreateEvent或CreateEvent函数,或CreateSemaphore或者CreateMutex函数,这些函数调用返回的句柄值都保存在WaitHandle基类定义的SafeWaitHandle字段中。
**二、事件(Event)类实现线程同步**
**2.1 **AutoResetEvent** (自动重置事件)**
先讲讲AutoresetEvent类的构造函数,其定义为:
public AutoResetEvent(bool initialState);
构造函数中用一个bool 类型的初始状态来设置AutoResetEvent对象的状态,如果要将AutoResetEvent对象的初始状态设置为终止,则传入bool值为true,若要设置非终止,就传入false。
WaitOne方法定义:
public virtual bool WaitOne(int millisecondsTimeout);该方法用来阻塞线程,当在指定的时间间隔还没有收到一个信号时,将返回false。
调用Set方法发信号来释放等待线程。在使用过程中WaitOne方法和Set方法都是成对出现的, 一个用于阻塞线程,等待信号,一个用来释放等待线程(就是说调用set方法来发送一个信号,此时WaitOne接受到信号,就释放阻塞的线程,线程就可以继续运行)
线程通过调用AutoResetEvent的WaitOne方法来等待信号,如果AutoResetEvent对象为非终止状态,则线程被阻止,等到线程调用Set方法来恢复线程执行。如果AutoResetEvent为终止状态时,则线程不会被阻止,此时AutoResetEvent将立即释放线程并返回为非终止状态(指出有线程在使用资源的一种状态)。
下面通过通过一个例子来演示下AutoResetEvent的使用:
```
using System;
using System.Threading;
namespace KenelMode
{
class Program
{
// 初始化自动重置事件,并把状态设置为非终止状态
// 如果这里把初始状态设置为True时,
// 当调用WaitOne方法时就不会阻塞线程,看到的输出结果的时间就是一样的了
// 因为设置为True时,表示此时已经为终止状态了。
public static AutoResetEvent autoEvent = new AutoResetEvent(false);
static void Main(string[] args)
{
Console.WriteLine("Main Thread Start run at: " +DateTime.Now.ToLongTimeString());
Thread t = new Thread(TestMethod);
t.Start();
// 阻塞主线程3秒后
// 调用 Set方法释放线程,使线程t可以运行
Thread.Sleep(3000);
// Set 方法就是把事件状态设置为终止状态。
autoEvent.Set();
Console.Read();
}
public static void TestMethod()
{
autoEvent.WaitOne();
// 3秒后线程可以运行,所以此时显示的时间应该和主线程显示的时间相差3秒
Console.WriteLine("Method Restart run at: " + DateTime.Now.ToLongTimeString());
}
}
}
```
运行结果(从运行结果看确实是过了一秒后在TestMethod方法中的语句):
![](https://box.kancloud.cn/2016-01-23_56a2eb331e367.png)
上面中用到的是没有带参数的WaitOne方法,该方法表示无限制阻塞线程,直到收到一个事件为止(通过Set方法来发送一个信号),同时我们也可以设置堵塞线程的事件,当超时时,线程将不阻塞直接运行(尽管此时没有通过Set来发送一个信号,线程照样运行,只是WaitOne方法返回的的值不一样)。
bool WaitOne(int millisecondsTimeout) 收到信号时返回为True,没收到信号返回为false。
看完下面的代码你可能会形象理解WaitOne(millisecondsTimeout)方法的使用的:
```
using System;
using System.Threading;
namespace KenelMode
{
class Program
{
// 初始化自动重置事件,并把状态设置为非终止状态
// 如果这里把初始状态设置为True时,
// 当调用WaitOne方法时就不会阻塞线程,看到的输出结果的时间就是一样的了
// 因为设置为True时,表示此时已经为终止状态了。
public static AutoResetEvent autoEvent = new AutoResetEvent(false);
static void Main(string[] args)
{
Console.WriteLine("Main Thread Start run at: " +DateTime.Now.ToLongTimeString());
Thread t = new Thread(TestMethod);
t.Start();
// 阻塞主线程1秒后
// 调用 Set方法释放线程,使线程t可以运行
Thread.Sleep(3000);
// Set 方法就是把事件状态设置为终止状态。
autoEvent.Set();
Console.Read();
}
public static void TestMethod()
{
if (autoEvent.WaitOne(2000))
{
Console.WriteLine("Get Singal to Work");
// 3秒后线程可以运行,所以此时显示的时间应该和主线程显示的时间相差一秒
Console.WriteLine("Method Restart run at: " + DateTime.Now.ToLongTimeString());
}
else
{
Console.WriteLine("Time Out to work");
Console.WriteLine("Method Restart run at: " + DateTime.Now.ToLongTimeString());
}
}
}
}
```
运行结果:
![](https://box.kancloud.cn/2016-01-23_56a2eb332cacd.png)
同时这里可以把Thread.Sleep(3000)改成Thread.Sleep(1000)的时候,就是说AutoResetEvent对象在超时之前就接到信号了, 此时WaitOne(2000)放回的值就是True,就得到的是Get Singal to Work, 之间的事件间隔当然也是1秒了,在这里结果就不贴了。
**2.2 ManualResetEvent(手动重置事件)**
WaitOne while the AutoResetEvent is in the signaled state, the thread does not block." data-guid="89f5b5007b8c3bb9a7bd377a380ad62c">ManualResetEvent的使用和AutoResetEvent的使用很类似,因为他们都是从EventWaitHandle类派生的,不过他们还是有点区别:
WaitOne while the AutoResetEvent is in the signaled state, the thread does not block." data-guid="89f5b5007b8c3bb9a7bd377a380ad62c">AutoResetEvent 为终止状态时线程调用 WaitOne,则线程不会被阻止。AutoResetEvent releases the thread immediately and returns to the non-signaled state." data-guid="6b52aed4c2b560eeba356721b90fc103">**AutoResetEvent 将立即释放线程并返回到非终止状态,当再次调用WaitOne状态时线程会被阻止**
AutoResetEvent releases the thread immediately and returns to the non-signaled state." data-guid="6b52aed4c2b560eeba356721b90fc103">**这里请注意如果AutoResetEvent初始为非终止状态时, 调用WaitOne(int millisecondsTimeout)方法后并不会把状态返回为终止状态,此时还是非终止的,调用WaitOne方法自动改变状态只针对初始状态为终止状态时有效。**
AutoResetEvent releases the thread immediately and returns to the non-signaled state." data-guid="6b52aed4c2b560eeba356721b90fc103">然而ManualResetEvent初始状态为终止状态时时调用WaitOne,则线程同样不会被阻止,**但是ManualResetEvent的状态不会发生改变(当我再次调用WaitOne方法是一样不会阻止线程),需要我们手动终止()**
下面通过一段代码来说明两者的区别:
```
using System;
using System.Threading;
namespace ManualResetEventSample
{
class Program
{
// 初始化自动重置事件,并把状态设置为终止状态
public static AutoResetEvent autoEvent = new AutoResetEvent(true);
////public static ManualResetEvent autoEvent = new ManualResetEvent(true);
static void Main(string[] args)
{
Console.WriteLine("Main Thread Start run at: " + DateTime.Now.ToLongTimeString());
Thread t = new Thread(TestMethod);
t.Start();
Console.Read();
}
public static void TestMethod()
{
// 初始状态为终止状态,则第一次调用WaitOne方法不会堵塞线程
// 此时运行的时间间隔应该为0秒,但是因为是AutoResetEvent对象
// 调用WaitOne方法后立即把状态返回为非终止状态。
autoEvent.WaitOne();
Console.WriteLine("Method start at : "+ DateTime.Now.ToLongTimeString());
// 因为此时AutoRestEvent为非终止状态,所以调用WaitOne方法后将阻塞线程1秒,这里设置了超时时间
// 所以下面语句的和主线程中语句的时间间隔为1秒
// 当时 ManualResetEvent对象时,因为不会自动重置状态
// 所以调用完第一次WaitOne方法后状态仍然为非终止状态,所以再次调用不会阻塞线程,所以此时的时间间隔也为0
// 如果没有设置超时时间的话,下面这行语句将不会执行
autoEvent.WaitOne(1000);
Console.WriteLine("Method start at : " + DateTime.Now.ToLongTimeString());
}
}
}
```
运行结果:
![](https://box.kancloud.cn/2016-01-23_56a2eb333b2e8.png)
如果你把创建事件为手动重置事件ManualResetEvent时,得到的运行结果就会下面这样:
![](https://box.kancloud.cn/2016-01-23_56a2eb33554ac.png)
**2.3 跨进程之间同步**
内核模式的构造可同步在同一台机器上的不同进程中运行的线程,所以我们同样可以使用 AutoResetEvent实现不同进程中运行的线程同步,但是此时需要对AutoResetEvent进行命名,但是AutoResetEvent只提供带一个参数的构造函数的,此时应该如何去实现不同进程中的线程同步的呢?
其实是有解决办法的,因为AutoResetEvent是继承自EventWaitHandle类的,EventWaitHandle类有多个构造函数的
除了之前的方法创建AutoResetEvent对象外,
还可以通过EventWaitHandle AutoEvent = new EventWaitHandle (false, EventResetMode.Auto);这样的方法来构造AutoResetEvent对象,通过
EventWaitHandle autoEvent = new EventWaitHandle (false, EventResetMode.Auto,"My");方式就可以指定名称了
下面一段代码演示如何实现跨不同进程中的线程同步:
```
using System;
using System.Threading;
namespace CrossProcess_EventWaitHandle
{
class Program
{
public static EventWaitHandle autoEvent = new EventWaitHandle(true, EventResetMode.AutoReset, "My");
static void Main(string[] args)
{
Console.WriteLine("Main Thread Start run at: " + DateTime.Now.ToLongTimeString());
Thread t = new Thread(TestMethod);
// 为了有时间启动另外一个线程
Thread.Sleep(2000);
t.Start();
Console.Read();
}
public static void TestMethod()
{
// 进程一:显示的时间间隔为2秒
// 进程二中显示的时间间隔为3秒
// 因为进程二中AutoResetEvent的初始状态为非终止的
// 因为在进程一中通过WaitOne方法的调用已经把AutoResetEvent的初始状态返回为非终止状态了
autoEvent.WaitOne(1000);
Console.WriteLine("Method start at : "+ DateTime.Now.ToLongTimeString());
}
}
}
```
运行结果:
![](https://box.kancloud.cn/2016-01-23_56a2eb336a5d5.png)
本来打算在一篇文章里面讲述内核模式构造的,写着写着滚动条又变很小了,为了大家的阅读,我把信号量和互斥体放在后面一篇文章里面讲吧,相信后面的内容会很好理解的,因为后面两个类的使用和这篇中讲到的使用很类似,好歹都是继承WaitHandle类的。
- C# 基础知识系列
- C# 基础知识系列 专题一:深入解析委托——C#中为什么要引入委托
- C# 基础知识系列 专题二:委托的本质论
- C# 基础知识系列 专题三:如何用委托包装多个方法——委托链
- C# 基础知识系列 专题四:事件揭秘
- C# 基础知识系列 专题五:当点击按钮时触发Click事件背后发生的事情
- C# 基础知识系列 专题六:泛型基础篇——为什么引入泛型
- C# 基础知识系列 专题七: 泛型深入理解(一)
- C# 基础知识系列 专题八: 深入理解泛型(二)
- C# 基础知识系列 专题九: 深入理解泛型可变性
- C#基础知识系列 专题十:全面解析可空类型
- C# 基础知识系列 专题十一:匿名方法解析
- C#基础知识系列 专题十二:迭代器
- C#基础知识 专题十三:全面解析对象集合初始化器、匿名类型和隐式类型
- C# 基础知识系列 专题十四:深入理解Lambda表达式
- C# 基础知识系列 专题十五:全面解析扩展方法
- C# 基础知识系列 专题十六:Linq介绍
- C#基础知识系列 专题十七:深入理解动态类型
- 你必须知道的异步编程 C# 5.0 新特性——Async和Await使异步编程更简单
- 全面解析C#中参数传递
- C#基础知识系列 全面解析C#中静态与非静态
- C# 基础知识系列 C#中易混淆的知识点
- C#进阶系列
- C#进阶系列 专题一:深入解析深拷贝和浅拷贝
- C#进阶系列 专题二:你知道Dictionary查找速度为什么快吗?
- C# 开发技巧系列
- C# 开发技巧系列 使用C#操作Word和Excel程序
- C# 开发技巧系列 使用C#操作幻灯片
- C# 开发技巧系列 如何动态设置屏幕分辨率
- C# 开发技巧系列 C#如何实现图片查看器
- C# 开发技巧 如何防止程序多次运行
- C# 开发技巧 实现属于自己的截图工具
- C# 开发技巧 如何使不符合要求的元素等于离它最近的一个元素
- C# 线程处理系列
- C# 线程处理系列 专题一:线程基础
- C# 线程处理系列 专题二:线程池中的工作者线程
- C# 线程处理系列 专题三:线程池中的I/O线程
- C# 线程处理系列 专题四:线程同步
- C# 线程处理系列 专题五:线程同步——事件构造
- C# 线程处理系列 专题六:线程同步——信号量和互斥体
- C# 多线程处理系列专题七——对多线程的补充
- C#网络编程系列
- C# 网络编程系列 专题一:网络协议简介
- C# 网络编程系列 专题二:HTTP协议详解
- C# 网络编程系列 专题三:自定义Web服务器
- C# 网络编程系列 专题四:自定义Web浏览器
- C# 网络编程系列 专题五:TCP编程
- C# 网络编程系列 专题六:UDP编程
- C# 网络编程系列 专题七:UDP编程补充——UDP广播程序的实现
- C# 网络编程系列 专题八:P2P编程
- C# 网络编程系列 专题九:实现类似QQ的即时通信程序
- C# 网络编程系列 专题十:实现简单的邮件收发器
- C# 网络编程系列 专题十一:实现一个基于FTP协议的程序——文件上传下载器
- C# 网络编程系列 专题十二:实现一个简单的FTP服务器
- C# 互操作性入门系列
- C# 互操作性入门系列(一):C#中互操作性介绍
- C# 互操作性入门系列(二):使用平台调用调用Win32 函数
- C# 互操作性入门系列(三):平台调用中的数据封送处理
- C# 互操作性入门系列(四):在C# 中调用COM组件
- CLR
- 谈谈: String 和StringBuilder区别和选择
- 谈谈:程序集加载和反射
- 利用反射获得委托和事件以及创建委托实例和添加事件处理程序
- 谈谈:.Net中的序列化和反序列化
- C#设计模式
- UML类图符号 各种关系说明以及举例
- C#设计模式(1)——单例模式
- C#设计模式(2)——简单工厂模式
- C#设计模式(3)——工厂方法模式
- C#设计模式(4)——抽象工厂模式
- C#设计模式(5)——建造者模式(Builder Pattern)
- C#设计模式(6)——原型模式(Prototype Pattern)
- C#设计模式(7)——适配器模式(Adapter Pattern)
- C#设计模式(8)——桥接模式(Bridge Pattern)
- C#设计模式(9)——装饰者模式(Decorator Pattern)
- C#设计模式(10)——组合模式(Composite Pattern)
- C#设计模式(11)——外观模式(Facade Pattern)
- C#设计模式(12)——享元模式(Flyweight Pattern)
- C#设计模式(13)——代理模式(Proxy Pattern)
- C#设计模式(14)——模板方法模式(Template Method)
- C#设计模式(15)——命令模式(Command Pattern)
- C#设计模式(16)——迭代器模式(Iterator Pattern)
- C#设计模式(17)——观察者模式(Observer Pattern)
- C#设计模式(18)——中介者模式(Mediator Pattern)
- C#设计模式(19)——状态者模式(State Pattern)
- C#设计模式(20)——策略者模式(Stragety Pattern)
- C#设计模式(21)——责任链模式
- C#设计模式(22)——访问者模式(Vistor Pattern)
- C#设计模式(23)——备忘录模式(Memento Pattern)
- C#设计模式总结
- WPF快速入门系列
- WPF快速入门系列(1)——WPF布局概览
- WPF快速入门系列(2)——深入解析依赖属性
- WPF快速入门系列(3)——深入解析WPF事件机制
- WPF快速入门系列(4)——深入解析WPF绑定
- WPF快速入门系列(5)——深入解析WPF命令
- WPF快速入门系列(6)——WPF资源和样式
- WPF快速入门系列(7)——深入解析WPF模板
- WPF快速入门系列(8)——MVVM快速入门
- WPF快速入门系列(9)——WPF任务管理工具实现
- ASP.NET 开发
- ASP.NET 开发必备知识点(1):如何让Asp.net网站运行在自定义的Web服务器上
- ASP.NET 开发必备知识点(2):那些年追过的ASP.NET权限管理
- ASP.NET中实现回调
- 跟我一起学WCF
- 跟我一起学WCF(1)——MSMQ消息队列
- 跟我一起学WCF(2)——利用.NET Remoting技术开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(4)——第一个WCF程序
- 跟我一起学WCF(5)——深入解析服务契约 上篇
- 跟我一起学WCF(6)——深入解析服务契约 下篇
- 跟我一起学WCF(7)——WCF数据契约与序列化详解
- 跟我一起学WCF(8)——WCF中Session、实例管理详解
- 跟我一起学WCF(9)——WCF回调操作的实现
- 跟我一起学WCF(10)——WCF中事务处理
- 跟我一起学WCF(11)——WCF中队列服务详解
- 跟我一起学WCF(12)——WCF中Rest服务入门
- 跟我一起学WCF(13)——WCF系列总结
- .NET领域驱动设计实战系列
- .NET领域驱动设计实战系列 专题一:前期准备之EF CodeFirst
- .NET领域驱动设计实战系列 专题二:结合领域驱动设计的面向服务架构来搭建网上书店
- .NET领域驱动设计实战系列 专题三:前期准备之规约模式(Specification Pattern)
- .NET领域驱动设计实战系列 专题四:前期准备之工作单元模式(Unit Of Work)
- .NET领域驱动设计实战系列 专题五:网上书店规约模式、工作单元模式的引入以及购物车的实现
- .NET领域驱动设计实战系列 专题六:DDD实践案例:网上书店订单功能的实现
- .NET领域驱动设计实战系列 专题七:DDD实践案例:引入事件驱动与中间件机制来实现后台管理功能
- .NET领域驱动设计实战系列 专题八:DDD案例:网上书店分布式消息队列和分布式缓存的实现
- .NET领域驱动设计实战系列 专题九:DDD案例:网上书店AOP和站点地图的实现
- .NET领域驱动设计实战系列 专题十:DDD扩展内容:全面剖析CQRS模式实现
- .NET领域驱动设计实战系列 专题十一:.NET 领域驱动设计实战系列总结