写本系列的想法源自: 改善 C#程序的建议6:在线程同步中使用信号量,但是其中只是提到了AutoResetEvent、ManualResetEvent的用法,我觉得不够全面,因此想对.net下的同步方法做个逐一介绍。
本文主要参考:Beginner's Guide to Threading in .NET: Part 3 of n,可以说是对于原文修改再加上个人理解和拆分后的译作。原文作者Sacha Barber,为文甚为严谨周详,相当佩服。
闲话少说,本系列有以下几篇:
- 1)EventWaitHandle
- 2)Semaphores
- 3)Mutex
- 4)关键区域(Critical Sections)
- 5)其它同步对象
本文为1)EventWaitHandle。
让我们先来看看,WaitHandle与EventWaitHandle,Semaphores 和 Mutex的层次关系:
.Net中WaitHandle
采用信号与等待机制。当一个线程在WaitHandle
上等待时,该线程被阻塞,直到这个WaitHandle
接收到信号,此时等待线程解除阻塞,继续运行。WaitHandle
的工作机制可与铁路交叉口的情况进行类比,如下图所示:
Net中与WaitHandle相关的类层次如下:
System.Threading.WaitHandle
System.Threading.EventWaitHandle
System.Threading.Mutex
System.Threading.Semaphore
从上面的层次关系中可以看出, System.Threading.WaitHandle
是其它几个我们常用同步类的基类. 在介绍其子类前,我们先介绍几个重要的常用函数。
WaitAll (Static method on WaitHandle):可以将一组WaitHandle作为参数 传给该函数,而这组WaitHandle都将等待信号。
WaitAny (Static method on WaitHandle):可以将一组WaitHandle作为参数 传给该函数,不同的是,只要该组中的任何一个WaitHandle接收到信号,则函数将退出。
WaitOne:调用该函数的WaitHandle将被阻塞,直到接收到信号。
下面我们将专注于 System.Threading.EventWaitHandle
.对于EventWaitHandle
,大多数情况下,我们主要使用其两个子类:ManualResetEvent
和AutoResetEvent
。
AutoResetEvent
From MSDN:
"A thread waits for a signal by calling
WaitOne
on theAutoResetEvent
. If theAutoResetEvent
is in the non-signaled state, the thread blocks, waiting for the thread that currently controls the resource to signal that the resource is available by callingSet
.Calling
Set
signalsAutoResetEvent
to release a waiting thread.AutoResetEvent
remains signaled until a single waiting thread is released, and then automatically returns to the non-signaled state. If no threads are waiting, the state remains signaled indefinitely."
当我们使用AutoResetEvent
时,如果调用了Set,则其处于signaled状态,那么第一个解除阻塞的线程会自动将AutoResetEvent
置为non-signaled状态,那么其它等待AutoResetEvent
的线程要一直等待,直到AutoResetEvent
下一次接收到信号。示例代码:
using System;
using System.Threading;
namespace AutoResetEventTest
{
class Program
{
public static Thread T1;
public static Thread T2;
//This AutoResetEvent starts out non-signalled
public static AutoResetEvent ar1 = new AutoResetEvent(false);
//This AutoResetEvent starts out signalled
public static AutoResetEvent ar2 = new AutoResetEvent(true);
static void Main(string[] args)
{
T1 = new Thread((ThreadStart)delegate
{
Console.WriteLine(
"T1 is simulating some work by sleeping for 5 secs");
//calling sleep to simulate some work
Thread.Sleep(5000);
Console.WriteLine(
"T1 is just about to set AutoResetEvent ar1");
//alert waiting thread(s)
ar1.Set();
});
T2 = new Thread((ThreadStart)delegate
{
//wait for AutoResetEvent ar1, this will wait for ar1 to be signalled
//from some other thread
Console.WriteLine(
"T2 starting to wait for AutoResetEvent ar1, at time {0}",
DateTime.Now.ToLongTimeString());
ar1.WaitOne();
Console.WriteLine(
"T2 finished waiting for AutoResetEvent ar1, at time {0}",
DateTime.Now.ToLongTimeString());
//wait for AutoResetEvent ar2, this will skip straight through
//as AutoResetEvent ar2 started out in the signalled state
Console.WriteLine(
"T2 starting to wait for AutoResetEvent ar2, at time {0}",
DateTime.Now.ToLongTimeString());
ar2.WaitOne();
Console.WriteLine(
"T2 finished waiting for AutoResetEvent ar2, at time {0}",
DateTime.Now.ToLongTimeString());
});
T1.Name = "T1";
T2.Name = "T2";
T1.Start();
T2.Start();
Console.ReadLine();
}
}
}
结果如下图:
ManualResetEvent
From MSDN:
"When a thread begins an activity that must complete before other threads proceed, it calls
Reset
to putManualResetEvent
in the non-signaled state. This thread can be thought of as controlling theManualResetEvent
. Threads that callWaitOne
on theManualResetEvent
will block, awaiting the signal. When the controlling thread completes the activity, it calls Se
t to signal that the waiting threads can proceed. All waiting threads are released.Once it has been signaled,
ManualResetEvent
remains signaled until it is manually reset. That is, calls toWaitOne
return immediately."
在使用ManualResetEvent
时,如果ManualResetEvent
被置为signaled状态,那么在其上等待的所有线程都被解除阻塞直到ManualResetEvent
又被重置,即处于non-signaled状态。示例代码如下:
using System;
using System.Threading;
namespace ManualResetEventTest
{
/// <summary>
/// This simple class demonstrates the usage of an ManualResetEvent
/// in 2 different scenarios, bith in the non-signalled state and the
/// signalled state
/// </summary>
class Program
{
public static Thread T1;
public static Thread T2;
public static Thread T3;
//This ManualResetEvent starts out non-signalled
public static ManualResetEvent mr1 = new ManualResetEvent(false);
static void Main(string[] args)
{
T1 = new Thread((ThreadStart)delegate
{
Console.WriteLine(
"T1 is simulating some work by sleeping for 5 secs");
//calling sleep to simulate some work
Thread.Sleep(5000);
Console.WriteLine(
"T1 is just about to set ManualResetEvent ar1");
//alert waiting thread(s)
mr1.Set();
});
T2 = new Thread((ThreadStart)delegate
{
//wait for ManualResetEvent mr1, this will wait for ar1
//to be signalled from some other thread
Console.WriteLine(
"T2 starting to wait for ManualResetEvent mr1, at time {0}",
DateTime.Now.ToLongTimeString());
mr1.WaitOne();
Console.WriteLine(
"T2 finished waiting for ManualResetEvent mr1, at time {0}",
DateTime.Now.ToLongTimeString());
});
T3 = new Thread((ThreadStart)delegate
{
//wait for ManualResetEvent mr1, this will wait for ar1
//to be signalled from some other thread
Console.WriteLine(
"T3 starting to wait for ManualResetEvent mr1, at time {0}",
DateTime.Now.ToLongTimeString());
mr1.WaitOne();
Console.WriteLine(
"T3 finished waiting for ManualResetEvent mr1, at time {0}",
DateTime.Now.ToLongTimeString());
});
T1.Name = "T1";
T2.Name = "T2";
T3.Name = "T3";
T1.Start();
T2.Start();
T3.Start();
Console.ReadLine();
}
}
}