上一篇介绍了通过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的两个子类。

  1. public EventWaitHandle(bool initialState, EventResetMode mode);
  2.  
  3. public enum EventResetMode
  4. {
  5. AutoReset = ,
  6. ManualReset = ,
  7. }

使用AutoResetEvent

AutoResetEvent 允许线程通过发信号互相通信:

  • 线程通过调用 AutoResetEvent 上的 WaitOne 来等待信号。 如果 AutoResetEvent 为非终止状态,则线程会被阻止,并等待当前控制资源的线程通过调用 Set 来通知资源可用。
  • 线程通过调用 Set 向 AutoResetEvent 发信号以释放等待线程。 AutoResetEvent 将保持终止状态,直到一个正在等待的线程被释放,然后自动返回非终止状态。 如果没有任何线程在等待,则状态将无限期地保持为终止状态。

可以通过将一个布尔值传递给构造函数来控制 AutoResetEvent 的初始状态:如果初始状态为终止状态,则为 true;否则为 false。

下面看一个例子:

  1. namespace AutoResetEventTest
  2. {
  3. class Program
  4. {
  5. //AutoResetEvent实例初始为非终止状态
  6. private static AutoResetEvent autoResetEvent = new AutoResetEvent(false);
  7.  
  8. static void Main(string[] args)
  9. {
  10. new Thread(() =>
  11. {
  12. while (true)
  13. {
  14. //调用WaitOne来等待信号,并设置超时时间为5秒
  15. bool status = autoResetEvent.WaitOne();
  16. if (status)
  17. {
  18. Console.WriteLine("ThreadOne get the signal");
  19. }
  20. else
  21. {
  22. Console.WriteLine("ThreadOne timeout(5 seconds) waiting for signal");
  23. break;
  24. }
  25. }
  26. Console.WriteLine("ThreadOne Exit");
  27.  
  28. }).Start();
  29.  
  30. new Thread(() =>
  31. {
  32. while (true)
  33. {
  34. //调用WaitOne来等待信号,并设置超时时间为5秒
  35. bool status = autoResetEvent.WaitOne();
  36. if (status)
  37. {
  38. Console.WriteLine("ThreadTwo get the signal");
  39. }
  40. else
  41. {
  42. Console.WriteLine("ThreadTwo timeout(5 seconds) waiting for signal");
  43. break;
  44. }
  45. }
  46. Console.WriteLine("ThreadTwo Exit");
  47.  
  48. }).Start();
  49.  
  50. Random ran = new Random();
  51. for (int i = ; i < ; i++)
  52. {
  53.  
  54. Thread.Sleep(ran.Next(, ));
  55. //通过Set向 AutoResetEvent 发信号以释放等待线程
  56. Console.WriteLine("Main thread send the signal");
  57. autoResetEvent.Set();
  58. }
  59.  
  60. Console.Read();
  61. }
  62. }
  63. }

代码的输出为下,通过结果也可以验证,每次调用Set方法之后,AutoResetEvent 将保持终止状态,直到一个正在等待的线程被释放,然后自动返回非终止状态。

使用ManualResetEvent

像AutoResetEvent一样,ManualResetEvent 也是线程通过发信号互相通信:

  • 线程通过调用 ManualResetEvent上的 WaitOne 来等待信号。 如果 ManualResetEvent为非终止状态,则线程会被阻止,并等待当前控制资源的线程通过调用 Set 来通知资源可用。
  • 线程通过调用 Set 向 ManualResetEvent发信号以释放等待线程。 与AutoResetEvent不同的是,ManualResetEvent将一直保持终止状态,并释放所有等待线程,直到有线程通过Reset将 ManualResetEvent 置于非终止状态。
  • 线程通过调用 Reset以将 ManualResetEvent 置于非终止状态。

可以通过将布尔值传递给构造函数来控制 ManualResetEvent 的初始状态,如果初始状态处于终止状态,为 true;否则为 false。

看一个例子:

  1. namespace ManualResetEventTest
  2. {
  3. class Program
  4. {
  5. //ManualResetEvent实例初始为非终止状态
  6. private static ManualResetEvent manualResetEvent = new ManualResetEvent(false);
  7.  
  8. static void Main(string[] args)
  9. {
  10. new Thread(() =>
  11. {
  12. //调用WaitOne来等待信号
  13. manualResetEvent.WaitOne();
  14. Console.WriteLine("Thread get the signal - the first time");
  15.  
  16. Thread.Sleep();
  17. manualResetEvent.WaitOne();
  18. Console.WriteLine("Thread get the signal - the second time");
  19.  
  20. //调用Reset来以将 ManualResetEvent 置于非终止状态
  21. Console.WriteLine("Child thread reset ManualResetEvent to non-signaled");
  22. manualResetEvent.Reset();
  23.  
  24. manualResetEvent.WaitOne();
  25. Console.WriteLine("Thread get the signal - the third time");
  26.  
  27. Console.WriteLine("Child thread reset ManualResetEvent to non-signaled");
  28. manualResetEvent.Reset();
  29.  
  30. //调用WaitOne来等待信号,并设置超时时间为3秒
  31. manualResetEvent.WaitOne();
  32. Console.WriteLine("timeout while waiting for signal");
  33. }).Start();
  34.  
  35. //通过Set向 ManualResetEvent 发信号以释放等待线程
  36. Console.WriteLine("Main thread set ManualResetEvent to signaled");
  37. manualResetEvent.Set();
  38. Thread.Sleep();
  39. Console.WriteLine("Main thread set ManualResetEvent to signaled");
  40. manualResetEvent.Set();
  41.  
  42. Console.Read();
  43. }
  44. }
  45. }

代码的输出如下,通过结果验证了,每次调用Set都会将ManualResetEvent设置为终止状态,并释放所有等待线程。只有手动调用 Reset才能将 ManualResetEvent 置于非终止状态。

实现进程间线程同步

前一篇文章中介绍的lock和Monitor只能进行同一个进程中的线程同步。

但是,由于同步事件EventWaitHandle是基于内核事件的,所以说,它可以实现进程之间的线程同步。

基于前面AutoResetEvent的例子稍作修改:

  1. class Program
  2. {
  3.  
  4. private static EventWaitHandle eventWaitHandle;
  5.  
  6. private static bool newEventWaitHandleObj = true;
  7.  
  8. static void Main(string[] args)
  9. {
  10. string EventWaitHandleName = "EventWaitHandleTest";
  11. try
  12. {
  13. //尝试打开已有的同步事件
  14. eventWaitHandle = EventWaitHandle.OpenExisting("EventWaitHandleTest");
  15. newEventWaitHandleObj = false;
  16. }
  17. catch (WaitHandleCannotBeOpenedException e)
  18. {
  19. Console.WriteLine("EventWaitHandle named {0} is not exist, error message: {1}", EventWaitHandleName, e.Message);
  20. //实例化同步事件,初始为非终止状态,设置为自动重置模式
  21. eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, "EventWaitHandleTest");
  22. Console.WriteLine("Create EventWaitHandle {0}", EventWaitHandleName);
  23. newEventWaitHandleObj = true;
  24. }
  25.  
  26. new Thread(() =>
  27. {
  28. while (true)
  29. {
  30. //调用WaitOne来等待信号,并设置超时时间为5秒
  31. bool status = eventWaitHandle.WaitOne();
  32. if (status)
  33. {
  34. Console.WriteLine("ThreadOne get the signal");
  35. }
  36. else
  37. {
  38. Console.WriteLine("ThreadOne timeout(5 seconds) waiting for signal");
  39. break;
  40. }
  41. }
  42. Console.WriteLine("ThreadOne Exit");
  43.  
  44. }).Start();
  45.  
  46. new Thread(() =>
  47. {
  48. while (true)
  49. {
  50. //调用WaitOne来等待信号,并设置超时时间为5秒
  51. bool status = eventWaitHandle.WaitOne();
  52. if (status)
  53. {
  54. Console.WriteLine("ThreadTwo get the signal");
  55. }
  56. else
  57. {
  58. Console.WriteLine("ThreadTwo timeout(5 seconds) waiting for signal");
  59. break;
  60. }
  61. }
  62. Console.WriteLine("ThreadTwo Exit");
  63.  
  64. }).Start();
  65.  
  66. if (newEventWaitHandleObj)
  67. {
  68. Random ran = new Random();
  69. for (int i = ; i < ; i++)
  70. {
  71.  
  72. Thread.Sleep(ran.Next(, ));
  73. //通过Set向 AutoResetEvent 发信号以释放等待线程
  74. Console.WriteLine("Main thread send the signal");
  75. eventWaitHandle.Set();
  76. }
  77. }
  78.  
  79. Console.Read();
  80. }
  81. }

代码的输出为下,代码中通过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的更多相关文章

  1. 线程同步(AutoResetEvent与ManualResetEvent)

    前言 在我们编写多线程程序时,会遇到这样一个问题:在一个线程处理的过程中,需要等待另一个线程处理的结果才能继续往下执行.比如:有两个线程,一个用来接收Socket数据,另一个用来处理Socket数据, ...

  2. C# 应用 - 多线程 6) 处理同步数据之手动同步 AutoResetEvent 和 ManualResetEvent

    1. 类的关系 AutoResetEvent 和 ManualResetEvent 都继承自 System.Threading.EventWaitHandle 类(EventWaitHandle 继承 ...

  3. 线程中AutoResetEvent与ManualResetEvent的区别

    线程之间的通信是通过发信号来进行沟通的.. ManualResetEvent发送Set信后后,需要手动Reset恢复初始状态.而对于AutoResetEvent来说,当发送完毕Set信号后,会自动Re ...

  4. 多线程同步AutoResetEvent 和ManualResetEvent

  5. 重新想象 Windows 8 Store Apps (47) - 多线程之线程同步: Semaphore, CountdownEvent, Barrier, ManualResetEvent, AutoResetEvent

    [源码下载] 重新想象 Windows 8 Store Apps (47) - 多线程之线程同步: Semaphore, CountdownEvent, Barrier, ManualResetEve ...

  6. 基元线程同步构造 AutoResetEvent和ManualResetEvent 线程同步

    在.Net多线程编程中,AutoResetEvent和ManualResetEvent这两个类经常用到, 他们的用法很类似,但也有区别.ManualResetEvent和AutoResetEvent都 ...

  7. C#多线程同步事件及等待句柄AutoResetEvent 和 ManualResetEvent

    最近捣鼓了一下多线程的同步问题,发现其实C#关于多线程同步事件处理还是很灵活,这里主要写一下,自己测试的一些代码,涉及到了AutoResetEvent 和 ManualResetEvent,当然还有也 ...

  8. C# 使用ManualResetEvent 进行线程同步

    上一篇我们介绍了AutoResetEvent,这一篇我们来看下ManualResetEvent ,顾名思义ManualResetEvent  为手动重置事件. AutoResetEvent和Manua ...

  9. C# 线程间互相通信 AutoResetEvent和ManualResetEvent

    C#线程间互相通信主要用到两个类:AutoResetEvent和ManualResetEvent. 一.AutoResetEvent AutoResetEvent 允许线程通过发信号互相通信,线程通过 ...

随机推荐

  1. MongoDB 比较适用哪些业务场景?

      在云栖社区上发起了一个 MongoDB 使用场景及运维管理问题交流探讨的技术话题,有近5000人关注了该话题讨论,这里就 MongoDB 的使用场景做个简单的总结,谈谈什么场景该用 MongoDB ...

  2. linux 解压压缩大全

    eoiioe   linux下解压命令大全 .tar 解包:tar xvf FileName.tar打包:tar cvf FileName.tar DirName(注:tar是打包,不是压缩!)——— ...

  3. Java设计模式(14)责任链模式(Chain of Responsibility模式)

    Chain of Responsibility定义:Chain of Responsibility(CoR) 是用一系列类(classes)试图处理一个请求request,这些类之间是一个松散的耦合, ...

  4. USB2.0学习笔记连载(十四):USB驱动安装及固件程序的编写

    在之前的博客中已经讲过,驱动程序最核心的两个文件,一个是xxx.sys文件,一个是xxx.inf文件,主机是寻找xxx.inf文件. 在下面的文件中有相关关于USB驱动的说明.对于用户来说,xxx.s ...

  5. oauth2.0服务端与客户端搭建

    oauth2.0服务端与客户端搭建 - 推酷 今天搭建了oauth2.0服务端与客户端.把搭建的过程记录一下.具体实现的功能是:client.ruanwenwu.cn的用户能够通过 server.ru ...

  6. Python之生成二面体群元素

    from sympy.combinatorics.named_groups import DihedralGroup from collections import Counter n = 12 G ...

  7. 原创:用VBA实现将鼠标选择的单元格按照指定格式合并并复制到剪切板

    原创:用VBA实现将鼠标选择的单元格按照指定格式合并并复制到剪切板 一.主要实现以下功能:1.用鼠标选择单元格(可谓连续单元格,也可为不连续的)后,按照要求格式“证件号码:+选定内容+“,”+”选定内 ...

  8. VIM下的可视模式的相关知识

    三种可视模式: v 激活面向字符的可视模式: V 激活面向行的可视模式: ctrl+v 激活面向列块的可视模式: 选择高亮区: 上面的 v 是可以与跳转指令 以及表示范围的指令组合使用的. 如:vl, ...

  9. C# wkhtmltopdf 将html转pdf

    一.转换程序代码如下: public string HtmlToPdf(string url) { bool success = true; // string dwbh = url.Split('? ...

  10. 第1章:认识Shell脚本

     第一章:认识Shell脚本 1.1.什么是Shell Shell是系统的用户界面,提供了用户与内核进行交互操作的一种接口.它接收用户输入的命令并把它送入内核去执行. 实际上Shell是一个命令解释器 ...