多線程編程中的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類還擁有兩種模式,AutoResetManualReset 模式:

  • 在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類的用法幾乎相同,所以這里就不多說了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM