尊旭网
当前位置: 尊旭网 > 知识 >

resetevent

时间:2025-02-16 19:05:32 编辑:阿旭

C#AutoResetEvent和ManualResetEvent的区别

C#中的AutoResetEvent和ManualResetEvent用于实现线程同步。其基本工作原理是多个线程持有同一个XXXResetEvent,在这个XXXResetEvent未被set前,各线程都在WaitOne()除挂起;在这个XXXResetEvent被set后,所有被挂起的线程中有一个(AutoResetEvent的情况下)或全部(ManualResetEvent的情况下)恢复执行。

AutoResetEvent与ManualResetEvent的差别在于某个线程在WaitOne()被挂起后重新获得执行权时,是否自动reset这个事件(Event),前者是自动reset的,后者不是。所以从这个角度上也可以解释上段提到的“在这个XXXResetEvent被set后,所有被挂起的线程中有一个(AutoResetEvent的情况下)或全部(ManualResetEvent的情况下)恢复执行”——因为前者一旦被某个线程获取后,立即自动reset这个事件(Event),所以其他持有前者的线程之后WaitOne()时又被挂起;而后者被某个获取后,不会自动reset这个事件(Event),所以后续持有后者的线程在WaitOne()时不会被挂起。

namespace AutoResetEvent_Examples {
class MyMainClass {
/*
* 构造方法的参数设置成false后,表示创建一个没有被set的AutoResetEvent
* 这就导致所有持有这个AutoResetEvent的线程都会在WaitOne()处挂起
* 此时如果挂起的线程数比较多,那么你看一下自己的内存使用量……。
* 如果将参数设置成true,表示创建一个被set的AutoResetEvent
* 持有这个AutoResetEvent的线程们会竞争这个Event
* 此时,在其他条件满足的情况下
* 至少会有一个线程得到执行
* 而不是因得不到Event而导致所有线程都得不到执行
*/
static AutoResetEvent myResetEvent = new AutoResetEvent(false);
static int _Count = 0;
static void Main() {
Thread myThread = null;
for(int i = 0;i < 100;i++) {
myThread = new Thread(new ThreadStart(MyThreadProc));
myThread.Name = "Thread" + i;
myThread.Start();
}
myResetEvent.Set();
Console.Read();
}
static void MyThreadProc() {
myResetEvent.WaitOne();
_Count++;
Console.WriteLine("In thread:{0},label={1}.",Thread.CurrentThread.Name,_Count);

myResetEvent.Set();
}
}
}

namespace ManualResetEvent_Examples {
class MyMainClass {
/*
* 构造方法的参数设置成false后,表示创建一个没有被set的ManualResetEvent
* 这就导致所有持有这个ManualResetEvent的线程都会在WaitOne()处挂起
* 此时如果挂起的线程数比较多,那么你看一下自己的内存使用量……。
* 如果将参数设置成true,表示创建一个被set的ManualResetEvent
* 持有这个ManualResetEvent的线程们在其他条件满足的情况下
* 会同时得到执行(注意,是同时得到执行!所以本例中的_Count的结果一般是不正确的^_^)
* 而不是因得不到Event而导致所有线程都得不到执行
*/
static ManualResetEvent myResetEvent = new ManualResetEvent(false);
static int _Count = 0;
static void Main() {
Thread myThread = null;
for(int i = 0;i < 1000;i++) {
myThread = new Thread(new ThreadStart(MyThreadProc));
myThread.Name = "Thread" + i;
myThread.Start();
}
myResetEvent.Set();
Console.Read();
}
static void MyThreadProc() {
myResetEvent.WaitOne();
_Count++;
/*
* 在new ManualResetEvent(false);的情况下
* 下面的输出结果可能比较诡异:多个线程都输出label=1000!
* 一种可能的原因是多个线程在各自执行到_Count++后,被挂起
* 随后打印的_Count值就不是本线程中刚刚修改过的_Count值了。
*/
Console.WriteLine("In thread:{0},_Count={1}.",Thread.CurrentThread.Name,_Count);
}
}
}

set是让事件(Event)发生,而reset是让事件(Event)复位或者说忽略已经的事件(Event)。WaitOne是等待事件(Event)的发生,之后继续向下执行,否则一直等待。

在构造AutoResetEvent和ManualResetEvent的时候,它们的构造方法里需要一个参数initState,中文版MSDN(2005和2008)上的解释是“若要将初始状态设置为终止,则为 true;若要将初始状态设置为非终止,则为false。”,我看了一个下午,没弄明白,而看一下英文版后大概就明白了“A value that you set totrueto set the initial state of the specified event to signaled. Set this value tofalseto set the initial state of the event to nonsignaled.”(参见:http://msdn.microsoft.com/en-us/library/ee432364.aspx),大体意思是说这个参数决定是否在构造这个Event的时候就设置它为“发生”状态(signaled),如果是,则设置为true,也就是说持有这个Event的一个或多个线程在一开始就可以执行,而不需要挂起,至少是不会全部挂起(持有AutoResetEvent的一个或多个线程在任意时刻至多有一个线程在执行;持有ManualResetEvent的一个或多个线程会同时执行),否则为false(持有AutoResetEvent和ManualResetEvent的所有线程都将挂起,因为事件(Event)没有被set,即事件没有发生)。

另外稍微提一下,我在做多线程测试的时候,发现在线程数少的情况下,即使多个线程不做任何同步,如果对一个公共变量进行非互斥式修改时,不会至少很难出现不一致的情况,比如开100个线程,这个线程不做任何同步就分别给一个公共变量执行加1操作,那么结果在绝绝绝大部分的情况下是100!所以,我最后就下了狠心,把线程数增加到1000个,这个时候才出现问题,但问题也不是想象得那么严重——结果在991-1000之间!

再有,MSDN上对Monitor的Wait和Pulse两个方法用法的举例会导致死锁,一种死锁的执行顺序是:
1、线程tSecond在SecondThread()中执行到while(Monitor.Wait(m_smplQueue,1000))后,释放m_smplQueue的锁,线程tSecond挂起;
2、线程tFirst在FirstThread()中执行到Monitor.Wait(m_smplQueue)之前耗费的时间超过1000毫秒,此时线程tSecond退出,线程tFirst挂起,并且从此以后不会被恢复!
可以使用如下改动过的代码验证:

public void FirstThread() {
int counter = 0;
lock(m_smplQueue) {
Console.WriteLine("11");
while(counter < MAX_LOOP_TIME) {
//Wait, if the queue is busy.
Console.WriteLine("12");
Monitor.Wait(m_smplQueue);
Console.WriteLine("13");
//Push one element.
m_smplQueue.Enqueue(counter);
Console.WriteLine("14");
//Release the waiting thread.
Monitor.Pulse(m_smplQueue);
Console.WriteLine("15");
counter++;
Console.WriteLine("16");
}
}
}
public void SecondThread() {
lock(m_smplQueue) {
Console.WriteLine("21");
//Release the waiting thread.
Monitor.Pulse(m_smplQueue);
Console.WriteLine("22");
//Wait in the loop, while the queue is busy.
//Exit on the time-out when the first thread stops.
while(Monitor.Wait(m_smplQueue,1000)) {
Console.WriteLine("23");
//Pop the first element.
int counter = (int) m_smplQueue.Dequeue();
Console.WriteLine("24");
//Print the first element.
Console.WriteLine(counter.ToString());
Console.WriteLine("25");
//Release the waiting thread.
Monitor.Pulse(m_smplQueue);
Console.WriteLine("26");
}
Console.WriteLine("27");
}
}


C#AutoResetEvent和ManualResetEvent的区别

C#中的AutoResetEvent和ManualResetEvent用于实现线程同步。其基本工作原理是多个线程持有同一个XXXResetEvent,在这个XXXResetEvent未被set前,各线程都在WaitOne()除挂起;在这个XXXResetEvent被set后,所有被挂起的线程中有一个(AutoResetEvent的情况下)或全部(ManualResetEvent的情况下)恢复执行。

AutoResetEvent与ManualResetEvent的差别在于某个线程在WaitOne()被挂起后重新获得执行权时,是否自动reset这个事件(Event),前者是自动reset的,后者不是。所以从这个角度上也可以解释上段提到的“在这个XXXResetEvent被set后,所有被挂起的线程中有一个(AutoResetEvent的情况下)或全部(ManualResetEvent的情况下)恢复执行”——因为前者一旦被某个线程获取后,立即自动reset这个事件(Event),所以其他持有前者的线程之后WaitOne()时又被挂起;而后者被某个获取后,不会自动reset这个事件(Event),所以后续持有后者的线程在WaitOne()时不会被挂起。


namespace AutoResetEvent_Examples {
class MyMainClass {
/*
* 构造方法的参数设置成false后,表示创建一个没有被set的AutoResetEvent
* 这就导致所有持有这个AutoResetEvent的线程都会在WaitOne()处挂起
* 此时如果挂起的线程数比较多,那么你看一下自己的内存使用量……。
* 如果将参数设置成true,表示创建一个被set的AutoResetEvent
* 持有这个AutoResetEvent的线程们会竞争这个Event
* 此时,在其他条件满足的情况下
* 至少会有一个线程得到执行
* 而不是因得不到Event而导致所有线程都得不到执行
*/
static AutoResetEvent myResetEvent = new AutoResetEvent(false);
static int _Count = 0;
static void Main() {
Thread myThread = null;
for(int i = 0;i < 100;i++) {
myThread = new Thread(new ThreadStart(MyThreadProc));
myThread.Name = "Thread" + i;
myThread.Start();
}
myResetEvent.Set();
Console.Read();
}
static void MyThreadProc() {
myResetEvent.WaitOne();
_Count++;
Console.WriteLine("In thread:{0},label={1}.",Thread.CurrentThread.Name,_Count);

myResetEvent.Set();
}
}
}


namespace ManualResetEvent_Examples {
class MyMainClass {
/*
* 构造方法的参数设置成false后,表示创建一个没有被set的ManualResetEvent
* 这就导致所有持有这个ManualResetEvent的线程都会在WaitOne()处挂起
* 此时如果挂起的线程数比较多,那么你看一下自己的内存使用量……。
* 如果将参数设置成true,表示创建一个被set的ManualResetEvent
* 持有这个ManualResetEvent的线程们在其他条件满足的情况下
* 会同时得到执行(注意,是同时得到执行!所以本例中的_Count的结果一般是不正确的^_^)
* 而不是因得不到Event而导致所有线程都得不到执行
*/
static ManualResetEvent myResetEvent = new ManualResetEvent(false);
static int _Count = 0;
static void Main() {
Thread myThread = null;
for(int i = 0;i < 1000;i++) {
myThread = new Thread(new ThreadStart(MyThreadProc));
myThread.Name = "Thread" + i;
myThread.Start();
}
myResetEvent.Set();
Console.Read();
}
static void MyThreadProc() {
myResetEvent.WaitOne();
_Count++;
/*
* 在new ManualResetEvent(false);的情况下
* 下面的输出结果可能比较诡异:多个线程都输出label=1000!
* 一种可能的原因是多个线程在各自执行到_Count++后,被挂起
* 随后打印的_Count值就不是本线程中刚刚修改过的_Count值了。
*/
Console.WriteLine("In thread:{0},_Count={1}.",Thread.CurrentThread.Name,_Count);
}
}
}


set是让事件(Event)发生,而reset是让事件(Event)复位或者说忽略已经的事件(Event)。WaitOne是等待事件(Event)的发生,之后继续向下执行,否则一直等待。

在构造AutoResetEvent和ManualResetEvent的时候,它们的构造方法里需要一个参数initState,中文版MSDN(2005和2008)上的解释是“若要将初始状态设置为终止,则为 true;若要将初始状态设置为非终止,则为false。”,我看了一个下午,没弄明白,而看一下英文版后大概就明白了“A value that you set totrueto set the initial state of the specified event to signaled. Set this value tofalseto set the initial state of the event to nonsignaled.”(参见:http://msdn.microsoft.com/en-us/library/ee432364.aspx),大体意思是说这个参数决定是否在构造这个Event的时候就设置它为“发生”状态(signaled),如果是,则设置为true,也就是说持有这个Event的一个或多个线程在一开始就可以执行,而不需要挂起,至少是不会全部挂起(持有AutoResetEvent的一个或多个线程在任意时刻至多有一个线程在执行;持有ManualResetEvent的一个或多个线程会同时执行),否则为false(持有AutoResetEvent和ManualResetEvent的所有线程都将挂起,因为事件(Event)没有被set,即事件没有发生)。

另外稍微提一下,我在做多线程测试的时候,发现在线程数少的情况下,即使多个线程不做任何同步,如果对一个公共变量进行非互斥式修改时,不会至少很难出现不一致的情况,比如开100个线程,这个线程不做任何同步就分别给一个公共变量执行加1操作,那么结果在绝绝绝大部分的情况下是100!所以,我最后就下了狠心,把线程数增加到1000个,这个时候才出现问题,但问题也不是想象得那么严重——结果在991-1000之间!

再有,MSDN上对Monitor的Wait和Pulse两个方法用法的举例会导致死锁,一种死锁的执行顺序是:
1、线程tSecond在SecondThread()中执行到while(Monitor.Wait(m_smplQueue,1000))后,释放m_smplQueue的锁,线程tSecond挂起;
2、线程tFirst在FirstThread()中执行到Monitor.Wait(m_smplQueue)之前耗费的时间超过1000毫秒,此时线程tSecond退出,线程tFirst挂起,并且从此以后不会被恢复!
可以使用如下改动过的代码验证:

public void FirstThread() {
int counter = 0;
lock(m_smplQueue) {
Console.WriteLine("11");
while(counter < MAX_LOOP_TIME) {
//Wait, if the queue is busy.
Console.WriteLine("12");
Monitor.Wait(m_smplQueue);
Console.WriteLine("13");
//Push one element.
m_smplQueue.Enqueue(counter);
Console.WriteLine("14");
//Release the waiting thread.
Monitor.Pulse(m_smplQueue);
Console.WriteLine("15");
counter++;
Console.WriteLine("16");
}
}
}
public void SecondThread() {
lock(m_smplQueue) {
Console.WriteLine("21");
//Release the waiting thread.
Monitor.Pulse(m_smplQueue);
Console.WriteLine("22");
//Wait in the loop, while the queue is busy.
//Exit on the time-out when the first thread stops.
while(Monitor.Wait(m_smplQueue,1000)) {
Console.WriteLine("23");
//Pop the first element.
int counter = (int) m_smplQueue.Dequeue();
Console.WriteLine("24");
//Print the first element.
Console.WriteLine(counter.ToString());
Console.WriteLine("25");
//Release the waiting thread.
Monitor.Pulse(m_smplQueue);
Console.WriteLine("26");
}
Console.WriteLine("27");
}
}


net 中多线程有几种实现方法?

1:UI线程。这个线程是操作系统自动创建的,你画了个winform,那么程序一启动,自然有了这么个线程。值得注意的是,你添加一个Timer控件,现实的多线程,实际上,依然在UI线程里。只是定时被Timer夺去控制权而已,本质上依然是单线程。另一个线索也可以论证:本来非UI线程想更新UI界面,是需要利用delegate,involk等来实现的,但是在timer控件的线程里,是不需要的。\x0d\x0a2:Thread thread = new Thread(obj.functionName); thread.start();\x0d\x0a这样自定义的线程是真正的多线程,它的使用也是最灵活的。不像Timer线程,精确度只有50ms。值得注意的是:如果需要启动的线程函数是带输入参数的,怎么办?\x0d\x0a有两个办法:\x0d\x0aA:你不是启动obj对象里的函数吗?在thread.start();之前,你先添加这句话 MyObject obj = new MyObject(int a ,int b); 这样,obj.functionName函数里可以直接使用a和b了。还有个方法,就是利用委托封装函数,然后thread.start(参数);具体代码如下:\x0d\x0a\x0d\x0a[ComVisibleAttribute(false)]\x0d\x0apublic delegate void ParameterizedThreadStart(Object obj)\x0d\x0a//这个Thread类的构造方法的定义如下:\x0d\x0apublic Thread(ParameterizedThreadStart start);\x0d\x0a\x0d\x0apublic static void myStaticParamThreadMethod(Object obj)\x0d\x0a{\x0d\x0a Console.WriteLine(obj);\x0d\x0a}\x0d\x0a \x0d\x0astatic void Main(string[] args)\x0d\x0a{\x0d\x0a Thread thread = new Thread(myStaticParamThreadMethod);\x0d\x0a thread.Start("通过委托的参数传值");\x0d\x0a}\x0d\x0a\x0d\x0a3:利用threadpool线程池技术。threadpool的主要原理是池里面的线程不会完成一个任务就消亡,而是会继续执行其他的任务,这减少了线程的消亡和生成的代价。\x0d\x0a主要是ThreadPool.QueueUserWorkItem()和ThreadPool.RegisterWaitForSingleObject(···)两个静态函数。具体如下:\x0d\x0aQueueUserWorkItem的使用:\x0d\x0astatic void ThreadProc(Object stateInfo)\x0d\x0a {\x0d\x0a Console.WriteLine("Hello from the thread pool.");\x0d\x0a }\x0d\x0aMain函数里ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc)); 即可。(注意WaitCallback系统委托),它的功能就像第2种方法里提到的new thread。\x0d\x0a那么RegisterWaitForSingleObject是干什么的呢?这个方法的做用是向线程池添加一个可以定时执行的方法。有点像第一种方法里提到的timer线程,却不属于UI线程。\x0d\x0a具体的使用如下:\x0d\x0aAutoResetEvent wait = new AutoResetEvent(false);\x0d\x0aobject state = new object();\x0d\x0aThreadPool.RegisterWaitForSingleObject(wait, new WaitOrTimerCallback(test), state, 5000, false);\x0d\x0a//5000是间隔调用的时间,也就是wait变量卡住的timeout时间(我觉得内部是这样实现的)\x0d\x0await.Set(); //如果有set这句话,那么第一次执行不用等5秒,则直接执行目标函数,否则没这句话,第一次执行要等5秒的。\x0d\x0a还有一个要注意:我平常使用的是ManualResetEvent,但在threadpool里,首先要选的是AutoResetEvent,因为AutoResetEvent能自动reset,所以下一次间隔来了,又要重新等待5秒钟,达到定时器的目的。如果是ManualResetEvent,要么一次执行不了(初始值为false),要么不间断的玩命执行。\x0d\x0aManualResetEvent和AutoResetEvent的另一个重要区别是前者能一次唤醒多个线程,而后者一次只能唤醒一个线程。\x0d\x0a其实RegisterWaitForSingleObject函数的使用有点想我封装好的MyTimer类的实现了:我里面的while死循环里用了个wait.waitone(2000,false);即可。\x0d\x0a对了,说到这里,RegisterWaitForSingleObject函数实现的定时器,如果手动停止呢?\x0d\x0a这要用到Unregister函数:\x0d\x0aRegisteredWaitHandle rw = ThreadPool.RegisterWaitForSingleObject(wait, new WaitOrTimerCallback(test), state, 3000, false);\x0d\x0arw.Unregister(wait);\x0d\x0a\x0d\x0a嗯讨论了这么多线程的东西,干脆再说一个小点:Thread.IsBackground=true的时候,指示该线程为后台线程。后台线程将会随着主线程的退出而退出


net 中多线程有几种实现方法

1:UI线程。这个线程是操作系统自动创建的,你画了个winform,那么程序一启动,自然有了这么个线程。值得注意的是,你添加一个Timer控件,现实的多线程,实际上,依然在UI线程里。只是定时被Timer夺去控制权而已,本质上依然是单线程。另一个线索也可以论证:本来非UI线程想更新UI界面,是需要利用delegate,involk等来实现的,但是在timer控件的线程里,是不需要的。
2:Thread thread = new Thread(obj.functionName); thread.start();
这样自定义的线程是真正的多线程,它的使用也是最灵活的。不像Timer线程,精确度只有50ms。值得注意的是:如果需要启动的线程函数是带输入参数的,怎么办?
有两个办法:
A:你不是启动obj对象里的函数吗?在thread.start();之前,你先添加这句话 MyObject obj = new MyObject(int a ,int b); 这样,obj.functionName函数里可以直接使用a和b了。还有个方法,就是利用委托封装函数,然后thread.start(参数);具体代码如下:

[ComVisibleAttribute(false)]
public delegate void ParameterizedThreadStart(Object obj)
//这个Thread类的构造方法的定义如下:
public Thread(ParameterizedThreadStart start);

public static void myStaticParamThreadMethod(Object obj)
{
Console.WriteLine(obj);
}

static void Main(string[] args)
{
Thread thread = new Thread(myStaticParamThreadMethod);
thread.Start("通过委托的参数传值");
}

3:利用threadpool线程池技术。threadpool的主要原理是池里面的线程不会完成一个任务就消亡,而是会继续执行其他的任务,这减少了线程的消亡和生成的代价。
主要是ThreadPool.QueueUserWorkItem()和ThreadPool.RegisterWaitForSingleObject(···)两个静态函数。具体如下:
QueueUserWorkItem的使用:
static void ThreadProc(Object stateInfo)
{
Console.WriteLine("Hello from the thread pool.");
}
Main函数里ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc)); 即可。(注意WaitCallback系统委托),它的功能就像第2种方法里提到的new thread。
那么RegisterWaitForSingleObject是干什么的呢?这个方法的做用是向线程池添加一个可以定时执行的方法。有点像第一种方法里提到的timer线程,却不属于UI线程。
具体的使用如下:
AutoResetEvent wait = new AutoResetEvent(false);
object state = new object();
ThreadPool.RegisterWaitForSingleObject(wait, new WaitOrTimerCallback(test), state, 5000, false);
//5000是间隔调用的时间,也就是wait变量卡住的timeout时间(我觉得内部是这样实现的)
wait.Set(); //如果有set这句话,那么第一次执行不用等5秒,则直接执行目标函数,否则没这句话,第一次执行要等5秒的。
还有一个要注意:我平常使用的是ManualResetEvent,但在threadpool里,首先要选的是AutoResetEvent,因为AutoResetEvent能自动reset,所以下一次间隔来了,又要重新等待5秒钟,达到定时器的目的。如果是ManualResetEvent,要么一次执行不了(初始值为false),要么不间断的玩命执行。
ManualResetEvent和AutoResetEvent的另一个重要区别是前者能一次唤醒多个线程,而后者一次只能唤醒一个线程。
其实RegisterWaitForSingleObject函数的使用有点想我封装好的MyTimer类的实现了:我里面的while死循环里用了个wait.waitone(2000,false);即可。
对了,说到这里,RegisterWaitForSingleObject函数实现的定时器,如果手动停止呢?
这要用到Unregister函数:
RegisteredWaitHandle rw = ThreadPool.RegisterWaitForSingleObject(wait, new WaitOrTimerCallback(test), state, 3000, false);
rw.Unregister(wait);

嗯讨论了这么多线程的东西,干脆再说一个小点:Thread.IsBackground=true的时候,指示该线程为后台线程。后台线程将会随着主线程的退出而退出