首先如果讀者是.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類的其中一個構造函數:
(
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.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類的用法幾乎相同,所以這里就不多說了。