多线程编程中的EventWaitHandler
首先如果读者是.Net多线程编程的老手,就不用看这篇文章了,这篇文章主要是阐述EventWaitHandler的一些基本原理和用法。
在.NET的System.Threading命名空间中有一个名叫WaitHandler的类,这是一个抽象类(abstract),我们无法手动去创建它,但是WaitHandler有三个子类,这三个子类分别是:System.Threading.EventWaitHandle,System.Threading.Mutex,System.Threading.Semaphore,这三个类都是非Abstract的,可以由开发者来创建和使用,其中本文主要讨论的是其中的System.Threading.EventWaitHandle类。
EventWaitHandle类的用途是可以调用其WaitOne方法来阻塞线程的运行,直到得到一个信号(该信号由EventWaitHandle类的Set方法发出),然后释放线程让其不再阻塞继续运行。
EventWaitHandle类拥有两种状态,终止状态 和 非终止状态:
- 在终止状态下,被WaitOne阻塞的线程会逐个得到释放,所以当EventWaitHandle始终处于终止状态时,调用其WaitOne方法无法起到阻塞线程的作用,因为线程被其WaitOne方法阻塞后,会立即被释放掉。
- 在非终止状态下,被WaitOne阻塞的线程会继续被阻塞,如果一个线程在EventWaitHandle对象处于非终止状态时调用了其WaitOne函数,该线程会立即被阻塞。
需要注意的是终止状态和非终止状态之间,是可以相互转换的。调用EventWaitHandle对象的Set方法既可以将EventWaitHandle对象设置为终止状态,调用EventWaitHandle对象的Reset方法既可以将EventWaitHandle对象设置为非终止状态。
此外,EventWaitHandle类还拥有两种模式,AutoReset 和 ManualReset 模式:
- 在AutoReset模式下,当EventWaitHandle对象被置为终止状态时,释放一个被WaitOne阻塞的线程后,EventWaitHandle对象会马上被设置为非终止状态,这个过程就等同于一个被WaitOne阻塞的线程被释放后,自动调用了EventWaitHandle的Reset方法,将EventWaitHandle对象自动从终止状态置回了非终止状态,所以这种模式叫AutoReset模式。所以如果有若干线程被EventWaitHandle对象的WaitOne方法阻塞了,每调用一次EventWaitHandle对象的Set方法将EventWaitHandle对象置为终止状态后,只能释放一个被阻塞的线程,然后EventWaitHandle对象又会被置为非终止状态。如果EventWaitHandle对象的Set方法之后又被调用了一次,剩下那些被阻塞的线程中,又会有一个线程被释放。所以如果有8个被WaitOne方法阻塞的线程,那么需要调用次EventWaitHandle对象的Set方法8次,才能让所有线程都得到释放。需要注意的一点就是MSDN中有提到:如果两次EventWaitHandle对象的Set方法调用非常接近,以至于当第一次调用Set方法后,被阻塞的线程还没有来得及释放,第二次Set调用又开始了,那么这两次Set方法的调用只会让一个被阻塞的线程被释放,也就是说如果两次Set方法的调用过于接近,那么就相当于只调用了一次。原因就是因为由于两次Set调用过于接近,当第一次Set调用后,其释放的线程还没有完全被释放,即EventWaitHandle对象还没有被置回非终止状态,第二次Set调用又开始了,又要求EventWaitHandle对象变成终止状态去释放剩余的阻塞线程,但是问题是现在EventWaitHandle对象本来就处于终止状态,并且第一次Set调用后的那个被释放的线程还没有被完全释放,所以现在不能去释放剩余的阻塞线程。之后待第一次Set调用后的那个被释放线程完全释放后,由于EventWaitHandle对象处于AutoReset模式,所以现在EventWaitHandle对象才会被置回非终止状态,那么就相当于第二次Set调用就白白浪费了一次机会去将EventWaitHandle对象置为终止状态去释放剩余的阻塞线程。
- 在ManualReset模式下,当EventWaitHandle对象被置为终止状态时,释放一个被WaitOne阻塞的线程后,其状态不会改变,仍然处于终止状态,所以当ManualReset模式下EventWaitHandle对象处于终止状态时,会连续释放所有被WaitOne方法阻塞的线程,直到手动调用其Reset方法将其置回非终止状态。所以这种模式叫ManualReset模式。
使用EventWaitHandle类的构造函数可以设置EventWaitHandle对象的模式和初始状态,以下是EventWaitHandle类的其中一个构造函数:
public EventWaitHandle
(
bool initialState,
EventResetMode mode
)
- 第一个参数initialState为true时,表示EventWaitHandle对象的初始状态为终止状态,反之false表示EventWaitHandle对象的初始状态为非终止状态。
- 第二个参数EventResetMode mode为EventResetMode.AutoReset时,表示EventWaitHandle对象处于AutoReset模式,当第二个参数为EventResetMode.ManualReset时,表示EventWaitHandle对象处于ManualReset模式。
需要注意的是,当EventWaitHandle对象的初始状态为终止状态,模式为AutoReset时,第一个被EventWaitHandle对象的WaitOne方法阻塞的线程会立即被释放,然后EventWaitHandle对象被置为非终止状态,如果后面还有线程被EventWaitHandle对象的WaitOne方法阻塞,就只有等到EventWaitHandle对象的Set方法调用,才能被释放了。原因我就不多说了,如果你还不明白请回去看上面对EventWaitHandle的终止状态和AutoReset模式的阐述。
下面是EventWaitHandle类的一个综合示例:
using System;
using System.Threading;
using System.Collections.Generic;
using System.Linq;
using System.Text; namespace EventWaitHandleDemo
{
class Program
{
static EventWaitHandle eHandle; static void UnblockDemo()
{
Console.WriteLine("测试EventWaitHandle的初始终止状态");
eHandle = new EventWaitHandle(true, EventResetMode.AutoReset);//eHandle初始为终止状态,模式为AutoReset
eHandle.WaitOne();//由于EventWaitHandle对象eHandle初始状态为终止状态,所以这里第一次调用WaitOne时阻塞被立即释放,又由于eHandle为AutoReset模式,所以之后eHandle会被置为非终止状态
Console.WriteLine("线程未被阻塞");
eHandle.WaitOne();//由于此时eHandle已经为非终止状态,所以此时调用WaitOne线程会被阻塞
Console.WriteLine("线程被阻塞");
} static void BlockDemo()
{
Console.WriteLine("测试EventWaitHandle的初始非终止状态");
eHandle = new EventWaitHandle(false, EventResetMode.AutoReset);//eHandle初始为非终止状态,模式为AutoReset
eHandle.WaitOne();//由于EventWaitHandle对象eHandle初始状态为非终止状态,所以这里第一次调用WaitOne时,线程就被组塞了
Console.WriteLine("线程被阻塞");
} static void AutoResetDemo()
{
Console.WriteLine("测试EventWaitHandle的AutoReset模式");
eHandle = new EventWaitHandle(false, EventResetMode.AutoReset);//eHandle初始为非终止状态,模式为AutoReset
ThreadPool.QueueUserWorkItem(new WaitCallback((object o) =>
{
//启动另一个线程,每隔3秒钟调用一次eHandle.Set方法,为主线程释放一次阻塞,一共释放3次
for (int i = 0; i < 3; i++)
{
Thread.Sleep(3000);
eHandle.Set();//由于eHandle处于AutoReset模式,所以每次使用Set将eHandle置为终止状态后,待被WaitOne阻塞的线程被释放后,eHandle又会被自动置回非终止状态
}
}), null); eHandle.WaitOne();//线程第一次被WaitOne阻塞
Console.WriteLine("第一次WaitOne调用阻塞已被释放,3秒后第二次WaitOne调用的阻塞会被释放");
eHandle.WaitOne();//线程第二次被WaitOne阻塞
Console.WriteLine("第二次WaitOne调用阻塞已被释放,3秒后第三次WaitOne调用的阻塞会被释放");
eHandle.WaitOne();//线程第三次被WaitOne阻塞
Console.WriteLine("第三次WaitOne调用阻塞已被释放,所有WaitOne调用的阻塞都已被释放");
} static void ManualResetDemo()
{
Console.WriteLine("测试EventWaitHandle的ManualReset模式");
eHandle = new EventWaitHandle(false, EventResetMode.ManualReset);//eHandle初始为非终止状态,模式为ManualReset
ThreadPool.QueueUserWorkItem(new WaitCallback((object o) =>
{
//启动另一线程,3秒后调用一次eHandle.Set方法,为主线程释放WaitOne阻塞
Thread.Sleep(3000);
eHandle.Set();//由于eHandle处于ManualReset模式,所以一旦使用Set将eHandle置为终止状态后,在eHandle的Reset被调用前eHandle会一直处于终止状态,在eHandle调用Reset前,所有被WaitOne阻塞的线程会立即得到释放
}), null); eHandle.WaitOne();//线程第一次被WaitOne阻塞
Console.WriteLine("第一次WaitOne调用阻塞已被释放,第二次WaitOne调用的阻塞会被立即释放");
eHandle.WaitOne();//线程第二次被WaitOne阻塞
Console.WriteLine("第二次WaitOne调用阻塞已被释放,第三次WaitOne调用的阻塞会被立即释放");
eHandle.WaitOne();//线程第三次被WaitOne阻塞
Console.WriteLine("第三次WaitOne调用阻塞已被释放,所有WaitOne调用的阻塞都已被释放"); eHandle.Reset();//调用eHandle的Reset方法,将eHandle手动置回非终止状态,之后再调用WaitOne方法就会被阻塞了
eHandle.WaitOne();//线程第四次被WaitOne阻塞
Console.WriteLine("第四次WaitOne调用阻塞已被释放");
} static void Main(string[] args)
{
Console.Write("你想测试哪一个方法1=UnblockDemo,2=BlockDemo,3=AutoResetDemo,4=ManualResetDemo:");
switch (Console.ReadLine())
{
case "1":
UnblockDemo();
break;
case "2":
BlockDemo();
break;
case "3":
AutoResetDemo();
break;
case "4":
ManualResetDemo();
break;
default:
break;
}
}
}
}
最后要提一下,那就是EventWaitHandle类还有两个子类: System.Threading.AutoResetEvent类 和 System.Threading.ManualResetEvent类:
AutoResetEvent类和EventWaitHandle类处于AutoReset模式时类似
ManualResetEvent类和EventWaitHandle类处于ManualReset模式时类似
这两个类和EventWaitHandle类的用法几乎相同,所以这里就不多说了。
转载至:https://www.cnblogs.com/OpenCoder/archive/2011/12/29/2305528.html
多线程编程中的EventWaitHandler的更多相关文章
- 关于python多线程编程中join()和setDaemon()的一点儿探究
关于python多线程编程中join()和setDaemon()的用法,这两天我看网上的资料看得头晕脑涨也没看懂,干脆就做一个实验来看看吧. 首先是编写实验的基础代码,创建一个名为MyThread的 ...
- Java多线程编程中Future模式的详解
Java多线程编程中,常用的多线程设计模式包括:Future模式.Master-Worker模式.Guarded Suspeionsion模式.不变模式和生产者-消费者模式等.这篇文章主要讲述Futu ...
- C++ 关于MFC多线程编程中的一些注意事项 及自定义消息的处理
在多线程编程中,最简单的方法,无非就是利用 AfxBeginThread 来创建一个工作线程,看一下这个函数的说明: CWinThread* AFXAPI AfxBeginThread( AFX_T ...
- Java多线程编程中Future模式的详解<转>
Java多线程编程中,常用的多线程设计模式包括:Future模式.Master-Worker模式.Guarded Suspeionsion模式.不变模式和生产者-消费者模式等.这篇文章主要讲述Futu ...
- Qt多线程编程中的对象线程与函数执行线程
近来用Qt编写一段多线程的TcpSocket通信程序,被其中Qt中报的几个warning搞晕了,一会儿是说“Cannot create children for a parent that is in ...
- Java多线程编程(2)--多线程编程中的挑战
一.串行.并发和并行 为了更清楚地解释这三个概念,我们来举一个例子.假设我们有A.B.C三项工作要做,那么我们有以下三种方式来完成这些工作: 第一种方式,先开始做工作A,完成之后再开始做工作B ...
- C#多线程编程中的锁系统
C#多线程编程中的锁系统(二) 上章主要讲排他锁的直接使用方式.但实际当中全部都用锁又太浪费了,或者排他锁粒度太大了. 这一次我们说说升级锁和原子操作. 目录 1:volatile 2: Inter ...
- 【C/C++开发】多线程编程中的join函数
多线程编程中的join函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 # coding: utf-8 # 测试多线程中join的 ...
- Linux下多线程编程中信号量介绍及简单使用
在Linux中有两种方法用于处理线程同步:信号量和互斥量. 线程的信号量是一种特殊的变量,它可以被增加或减少,但对其的关键访问被保证是原子操作.如果一个程序中有多个线程试图改变一个信号量的值,系统将保 ...
随机推荐
- Maven添加第三方库及部署配置
配置其实很简单,还是修改~/.m2/settings.xml文件,具体用文件说话,其他不解释. <?xml version="1.0" encoding="UTF- ...
- vue单页面应用项目优化总结(转载)
转载自:https://blog.csdn.net/qq_42221334/article/details/81907901这是之前在公司oa项目优化时罗列的优化点,基本都已经完成,当时花了点心思整理 ...
- 前端常用功能记录(三)—datatables表格初始化
其实上篇说的也算是jQuery Datatables的初始化,但主要是对某些字段意义的理解.下面记录的是datatables常用的功能的初始化. 数据源 我经常使用的有两种,一种是JavaScript ...
- 无焦点下获取条码枪返回值的Hook(再次改良版)
针对上一个版本再次改良,上除掉无用代码,新手绝对可以看懂! using System; using System.Collections.Generic; using System.Linq; usi ...
- Zabbix应用六:Zabbix监控Redis
利用Zabbix监控Redis Zabbix监控redis就比较简单了,因为zabbix官方提供了监控redis的模版和脚本,而且脚本有nodejs和python两种,下载地址:https://git ...
- 把jQuery的类、插件封装成seajs的模块的方法
这篇文章主要介绍了把jQuery的类.插件封装成seajs的模块的方法,需要的朋友可以参考下 注:本文使用的seajs版本是2.1.1 一.把Jquery封装成seajs的模块 define(func ...
- R9—R常用函数分类汇总
数据结构 一.数据管理 vector:向量 numeric:数值型向量 logical:逻辑型向量 character:字符型向量 list:列表 data.frame:数据框 c:连接为向量或列表 ...
- 第10月第1天 storyboard uitableviewcell
1. 如图,我们在Cell的属性界面对其进行了注册,identifier 为"TableViewCell" 不需要在 ViewDidLoad 对其进行注册了,如果进行注册的话,则对 ...
- Jenkins的安装及使用(二)
介绍两个方面:编译本地项目和拉取git代码并编译 在这之前,先要进行一个配置. 一.编译本地项目 开始添加任务,任务类型选择自由风格: 点击项目进入详情,源码管理选择无 在构建的地方选择项目,然后注意 ...
- mybatis输入输出映射——(五)
0.#{}与${}区别 #{}实现的是向prepareStatement中的预处理语句中设置参数值,sql语句中#{}表示一个占位符即?. <!-- 根据id查询用户信息 --> < ...