引言:

其实这部分内容应该是属于专题四,因为这篇也是讲关于线程同步的,但是由于考虑到用户的阅读习惯问题,因为文章太长了,很多人不是很愿意看包括我也是这样的,同时也有和我说可以把代码弄成折叠的,这样就不会太长的,但是我觉得这样也不怎么便于阅读,因为我看别人的博客的时候,看到有代码是折叠起来的时候很多时候不愿意去点,并且点一下之后同样拉长文章的,然后就看到右边的滚动条变小了,本以为快看完了(意思快学到知识了),一看滚动条后发现还有好长的内容很看, 所以就会给人一种不舒服的感觉吧(如果有和我一样的人的话,你肯定懂的是什么感觉的)。所以我把线程同步放到两篇文章里面来说,其实放到两篇文章里面也有一定原因的, 前面讲的线程同步主要是用户模式的(CLR Via C# 一书中是这么定义的,书中说到线程同步分两种:一、用户模式构造 二、内核模式构造,第一次看的时候不是很理解两个名词是什么意思的,我一般理解东西是采用把东西拆分来理解,理解拆分的各个部分后再合起来理解内容的,现在我对着两个的理解是——用户模式构造:对于内核模式构造(指的的是构造操作系内核对象),我们使用类(.net Framework中的类,如 AutoResetEvent, Semaphore类)的方法来实现线程同步,其实内部是调用操作系统的内核对象来实现的线程同步,此时就会导致线程从托管代码到为内核代码,然而用户模式构造,没有调用操作系统内核对象,线程只是在用户的托管代码上执行的),对于用户模式构造和内核模式的构造只是我自己的理解的, 如果有更好的理解方式可以留言告诉下我, 这样我们可以一起讨论和学习了。

目录

一、WaitHandle基类介绍

二、事件(Event)类实现线程同步

三、小结

一、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的使用:

  1. using System;
  2. using System.Threading;
  3. namespace KenelMode
  4. {
  5. class Program
  6. {
  7. // 初始化自动重置事件,并把状态设置为非终止状态
  8. // 如果这里把初始状态设置为True时,
  9. // 当调用WaitOne方法时就不会阻塞线程,看到的输出结果的时间就是一样的了
  10. // 因为设置为True时,表示此时已经为终止状态了。
  11. public static AutoResetEvent autoEvent = new AutoResetEvent(false);
  12. static void Main(string[] args)
  13. {
  14. Console.WriteLine("Main Thread Start run at: " +DateTime.Now.ToLongTimeString());
  15. Thread t = new Thread(TestMethod);
  16. t.Start();
  17. // 阻塞主线程3秒后
  18. // 调用 Set方法释放线程,使线程t可以运行
  19. Thread.Sleep(3000);
  20. // Set 方法就是把事件状态设置为终止状态。
  21. autoEvent.Set();
  22. Console.Read();
  23. }
  24. public static void TestMethod()
  25. {
  26. autoEvent.WaitOne();
  27. // 3秒后线程可以运行,所以此时显示的时间应该和主线程显示的时间相差3秒
  28. Console.WriteLine("Method Restart run at: " + DateTime.Now.ToLongTimeString());
  29. }
  30. }
  31. }

运行结果(从运行结果看确实是过了一秒后在TestMethod方法中的语句):

上面中用到的是没有带参数的WaitOne方法,该方法表示无限制阻塞线程,直到收到一个事件为止(通过Set方法来发送一个信号),同时我们也可以设置堵塞线程的事件,当超时时,线程将不阻塞直接运行(尽管此时没有通过Set来发送一个信号,线程照样运行,只是WaitOne方法返回的的值不一样)。

bool WaitOne(int millisecondsTimeout) 收到信号时返回为True,没收到信号返回为false。

看完下面的代码你可能会形象理解WaitOne(millisecondsTimeout)方法的使用的:

  1. using System;
  2. using System.Threading;
  3. namespace KenelMode
  4. {
  5. class Program
  6. {
  7. // 初始化自动重置事件,并把状态设置为非终止状态
  8. // 如果这里把初始状态设置为True时,
  9. // 当调用WaitOne方法时就不会阻塞线程,看到的输出结果的时间就是一样的了
  10. // 因为设置为True时,表示此时已经为终止状态了。
  11. public static AutoResetEvent autoEvent = new AutoResetEvent(false);
  12. static void Main(string[] args)
  13. {
  14. Console.WriteLine("Main Thread Start run at: " +DateTime.Now.ToLongTimeString());
  15. Thread t = new Thread(TestMethod);
  16. t.Start();
  17. // 阻塞主线程1秒后
  18. // 调用 Set方法释放线程,使线程t可以运行
  19. Thread.Sleep(3000);
  20. // Set 方法就是把事件状态设置为终止状态。
  21. autoEvent.Set();
  22. Console.Read();
  23. }
  24. public static void TestMethod()
  25. {
  26. if (autoEvent.WaitOne(2000))
  27. {
  28. Console.WriteLine("Get Singal to Work");
  29. // 3秒后线程可以运行,所以此时显示的时间应该和主线程显示的时间相差一秒
  30. Console.WriteLine("Method Restart run at: " + DateTime.Now.ToLongTimeString());
  31. }
  32. else
  33. {
  34. Console.WriteLine("Time Out to work");
  35. Console.WriteLine("Method Restart run at: " + DateTime.Now.ToLongTimeString());
  36. }
  37. }
  38. }
  39. }
运行结果:

同时这里可以把Thread.Sleep(3000)改成Thread.Sleep(1000)的时候,就是说AutoResetEvent对象在超时之前就接到信号了, 此时WaitOne(2000)放回的值就是True,就得到的是Get Singal to Work, 之间的事件间隔当然也是1秒了,在这里结果就不贴了。

2.2 ManualResetEvent(手动重置事件)

ManualResetEvent的使用和AutoResetEvent的使用很类似,因为他们都是从EventWaitHandle类派生的,不过他们还是有点区别:

AutoResetEvent 为终止状态时线程调用 WaitOne,则线程不会被阻止。AutoResetEvent 将立即释放线程并返回到非终止状态,当再次调用WaitOne状态时线程会被阻止

这里请注意如果AutoResetEvent初始为非终止状态时, 调用WaitOne(int millisecondsTimeout)方法后并不会把状态返回为终止状态,此时还是非终止的,调用WaitOne方法自动改变状态只针对初始状态为终止状态时有效。

然而ManualResetEvent初始状态为终止状态时时调用WaitOne,则线程同样不会被阻止,但是ManualResetEvent的状态不会发生改变(当我再次调用WaitOne方法是一样不会阻止线程),需要我们手动终止()

下面通过一段代码来说明两者的区别:

  1. using System;
  2. using System.Threading;
  3. namespace ManualResetEventSample
  4. {
  5. class Program
  6. {
  7. // 初始化自动重置事件,并把状态设置为终止状态
  8. public static AutoResetEvent autoEvent = new AutoResetEvent(true);
  9. public static ManualResetEvent autoEvent = new ManualResetEvent(true);
  10. static void Main(string[] args)
  11. {
  12. Console.WriteLine("Main Thread Start run at: " + DateTime.Now.ToLongTimeString());
  13. Thread t = new Thread(TestMethod);
  14. t.Start();
  15. Console.Read();
  16. }
  17. public static void TestMethod()
  18. {
  19. // 初始状态为终止状态,则第一次调用WaitOne方法不会堵塞线程
  20. // 此时运行的时间间隔应该为0秒,但是因为是AutoResetEvent对象
  21. // 调用WaitOne方法后立即把状态返回为非终止状态。
  22. autoEvent.WaitOne();
  23. Console.WriteLine("Method start at : "+ DateTime.Now.ToLongTimeString());
  24. // 因为此时AutoRestEvent为非终止状态,所以调用WaitOne方法后将阻塞线程1秒,这里设置了超时时间
  25. // 所以下面语句的和主线程中语句的时间间隔为1秒
  26. // 当时 ManualResetEvent对象时,因为不会自动重置状态
  27. // 所以调用完第一次WaitOne方法后状态仍然为非终止状态,所以再次调用不会阻塞线程,所以此时的时间间隔也为0
  28. // 如果没有设置超时时间的话,下面这行语句将不会执行
  29. autoEvent.WaitOne(1000);
  30. Console.WriteLine("Method start at : " + DateTime.Now.ToLongTimeString());
  31. }
  32. }
  33. }
运行结果:

如果你把创建事件为手动重置事件ManualResetEvent时,得到的运行结果就会下面这样:

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");方式就可以指定名称了

下面一段代码演示如何实现跨不同进程中的线程同步:

  1. using System;
  2. using System.Threading;
  3. namespace CrossProcess_EventWaitHandle
  4. {
  5. class Program
  6. {
  7. public static EventWaitHandle autoEvent = new EventWaitHandle(true, EventResetMode.AutoReset, "My");
  8. static void Main(string[] args)
  9. {
  10. Console.WriteLine("Main Thread Start run at: " + DateTime.Now.ToLongTimeString());
  11. Thread t = new Thread(TestMethod);
  12. // 为了有时间启动另外一个线程
  13. Thread.Sleep(2000);
  14. t.Start();
  15. Console.Read();
  16. }
  17. public static void TestMethod()
  18. {
  19. // 进程一:显示的时间间隔为2秒
  20. // 进程二中显示的时间间隔为3秒
  21. // 因为进程二中AutoResetEvent的初始状态为非终止的
  22. // 因为在进程一中通过WaitOne方法的调用已经把AutoResetEvent的初始状态返回为非终止状态了
  23. autoEvent.WaitOne(1000);
  24. Console.WriteLine("Method start at : "+ DateTime.Now.ToLongTimeString());
  25. }
  26. }
  27. }
运行结果:

三、小结

本来打算在一篇文章里面讲述内核模式构造的,写着写着滚动条又变很小了,为了大家的阅读,我把信号量和互斥体放在后面一篇文章里面讲吧,相信后面的内容会很好理解的,因为后面两个类的使用和这篇中讲到的使用很类似,好歹都是继承WaitHandle类的。

本文转自LearningHard 51CTO博客,原文链接:http://blog.51cto.com/learninghard/1034792,如需转载请自行联系原作者

[.Net线程处理系列]专题五:线程同步——事件构造相关推荐

  1. uwp post php,window_Win10开发系列专题五 UWP应用添加画布及语音输入支持,这是微软Win10十个开发系列专 - phpStudy...

    Win10开发系列专题五 UWP应用添加画布及语音输入支持 这是微软Win10十个开发系列专题的第五期内容,本期微软讲解了为Windows10 UWP应用添加画布/数字墨水书写及语音输入支持的方法.微 ...

  2. C#微信公众号开发系列教程五(接收事件推送与消息排重)

    C#微信公众号开发系列教程五(接收事件推送与消息排重) 原文:C#微信公众号开发系列教程五(接收事件推送与消息排重) 微信公众号开发系列教程一(调试环境部署) 微信公众号开发系列教程一(调试环境部署续 ...

  3. [C# 线程处理系列]专题四:线程同步

    目录: 一.线程同步概述 二.线程同步的使用 三 .总结 一.线程同步概述 前面的文章都是讲创建多线程来实现让我们能够更好的响应应用程序,然而当我们创建了多个线程时,就存在多个线程同时访问一个共享的资 ...

  4. [.Net线程处理系列]专题二:线程池中的工作者线程

    目录: 一.上节补充 二.CLR线程池基础 三.通过线程池的工作者线程实现异步 四.使用委托实现异步 五.任务 六.小结 一.上节补充 对于Thread类还有几个常用方法需要说明的. 1.1 Susp ...

  5. idea 线程内存_Java线程池系列之-Java线程池底层源码分析系列(一)

    课程简介: 课程目标:通过本课程学习,深入理解Java线程池,提升自身技术能力与价值. 适用人群:具有Java多线程基础的人群,希望深入理解线程池底层原理的人群. 课程概述:多线程的异步执行方式,虽然 ...

  6. idea 线程内存_Java线程池系列之-Java线程池底层源码分析系列(二)

    课程简介: 课程目标:通过本课程学习,深入理解Java线程池,提升自身技术能力与价值. 适用人群:具有Java多线程基础的人群,希望深入理解线程池底层原理的人群. 课程概述:多线程的异步执行方式,虽然 ...

  7. [C# 网络编程系列]专题五:TCP编程

    前言 前面专题的例子都是基于应用层上的HTTP协议的介绍, 现在本专题来介绍下传输层协议--TCP协议,主要介绍下TCP协议的工作过程和基于TCP协议的一个简单的通信程序,下面就开始本专题的正文了. ...

  8. [C# 基础知识系列]专题五:当点击按钮时触发Click事件背后发生的事情

    引言: 当我们在点击窗口中的Button控件VS会帮我们自动生成一些代码,我们只需要在Click方法中写一些自己的代码就可以实现触发Click事件后我们Click方法中代码就会执行,然而我一直有一个疑 ...

  9. VMware Horizon 8 2111 部署系列(五)配置事件数据库

     系列文章目录 VMware Horizon 8 2111 部署系列(一)规划_白昼ron的博客-CSDN博客 VMware Horizon 8 2111 部署系列(二)主备AD域控_白昼ron的博客 ...

最新文章

  1. CString 在_UNICODE宏定义下和普通ASCII编码下的不同
  2. 一次线上内存报警的研究
  3. 设计一个带有getmin功能的栈,保证时间复杂度在O(1)
  4. Remove Duplicates from Sorted ListII
  5. webService学习8:wsdl文档解释
  6. Facebook泄露隐私算什么?国内一次外卖,竟让4万用户全“裸着”
  7. SQL数据库语言基础之SqlServer数据库原理与设计课程期末复习-条件查询实例练习【学生表、选课表、家庭作业表】
  8. DirectX修复工具使用技巧之一——解除被占用的文件,完整修复C++
  9. 深入理解计算机系统寄存器寻址讲解
  10. 第一次使用拉勾网求职经历
  11. ecplise 设置代码自动提示功能
  12. oracle rac 各日志,oracle rac 日志体系结构!
  13. 《投射技术》与科学研究汉字笔迹心理学的希望
  14. 阿里、京东、拼多多都来了,互联网+疫苗是一门好生意吗?
  15. 直男福利!手把手教你做一只口红色号识别器,秒变李佳琦
  16. 《黑客与画家》读书笔记(三)
  17. WannaCry勒索病毒分析 **下**
  18. hive的UDF函数的使用。常见UDF函数
  19. [ 数据结构 ] 平衡二叉树(AVL)--------左旋、右旋、双旋
  20. 痞子衡嵌入式:嵌入式里串口(UART)自动波特率识别程序设计与实现

热门文章

  1. mysql dump 数据时间_使用mysqldump备份数据及做时间点还原测试步骤
  2. h3c交换机配置远程管理_H3C S3100交换机配置VLAN和远程管理
  3. 合肥天鹅湖万达广场机器人_合肥租房价位及租房地理位置推荐
  4. linux服务器操作系统日志都有哪些,Linux操作系统服务器日志管理详解
  5. php 获取key的位置,PHP获取当前所在目录位置的方法
  6. mysql一直copying to tmp table_mysql提示Copying to tmp table on disk
  7. 编程入门python java和c语言_学习编程适不适合从Python入门?哪种语言更适合入门?...
  8. oracle临时表经常被锁_【赵强老师】Oracle数据库的存储结构
  9. 使用randomaccessfile类将一个文本文件中的内容逆序输出_Java 中比较常用的知识点:I/O 总结...
  10. docker gpu报错Error response from daemon: could not select device driver ““ with capabilities: [[gpu]]