線程池是后台線程。每個線程都使用默認的堆棧大小,以默認的優先級運行,並處於多線程單元中。每個進程只有一個線程池對象。
下面說一下線程池中的異常,在線程池中未處理的異常將終止進程。以下為此規則的三種例外情況:
(1)由於調用了Abort,線程池線程中將引發ThreadAbortException異常(在對Abort方法進行調用時引發的異常)。
(2)由於正在卸載應用程序域,線程池線程中將引發AppDomainUnloadedException異常(在嘗試訪問已卸載的應用程序域時引發的異常)。
(3)公共語言運行庫或宿主進程將終止線程。
如果公共語言運行庫所創建的線程中未處理這些異常中的任何一個,則異常將終止線程,但公共語言運行庫不允許該異常繼續下去。
如果在主線程或從非托管代碼進入運行庫的線程中未處理這些異常,則它們將正常繼續,並導致應用程序終止。
注意:在 .NET Framework 1.0 和 1.1 版中,公共語言運行庫將捕獲線程池中的未處理異常,而不出現任何提示。這可能會破壞應用程序狀態,並最終導致應用程序掛起,將很難進行調試。
使用線程池的方式主要有4種,下面分別對其進行介紹。
1.ThreadPool類的QueueUserWorkItem方法
在使用線程池時,可以從托管代碼中調用ThreadPool類的QueueUserWorkItem方法,或從非托管代碼中調用CorQueueUserWorkItem方法,並用線程池線程要執行的回調方法WaitCallback執行線程池。
QueueUserWorkItem方法將方法排入隊列以便執行(並指定包含該方法所用數據的對象,用state參數來實現)。此方法在有線程池線程變得可用時執行。該方法有兩個語法形式,其語法如下:
public static bool QueueUserWorkItem(WaitCallback callBack) public static bool QueueUserWorkItem(WaitCallback callBack,Object state)
參數說明:
callBack:WaitCallback類型,它表示要執行的方法。
State:Object類型,包含方法所用數據的對象。
返回值:Boolean類型,如果此方法成功排隊,則為true;如果無法將該工作項排隊,則引發OutOfMemoryException異常。
示例 線程池的應用
本示例用線程池順序執行兩個方法。代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace ThreadPoolApple { class Program { public void thread1(Object obj)//定義方法thread1 { for (int i = 0; i <= 3; i++)//輸出0~3的值 { Console.Write(i.ToString()); } Console.WriteLine();//換行 } public void thread2(Object obj)//定義方法thread2 { for (int i = 4; i <= 6; i++)//輸出4~6的值 { Console.Write(i.ToString() + obj.ToString());//值后面加貨幣符號 } Console.WriteLine();//換行 } static void Main(string[] args) { string Ostr = "¥";//用字符串記錄貨幣符號 Program prog = new Program();//實例化Program類 for (int i = 0; i <= 3; i++) { //用線程池執行無參數方法 ThreadPool.QueueUserWorkItem(new WaitCallback(prog.thread1)); //用線程池執行有參數方法 ThreadPool.QueueUserWorkItem(new WaitCallback(prog.thread2),Ostr); } Console.ReadLine(); } } }
運行結果如下圖所示。
圖 線程池的應用結果
注意:在ThreadPool類中QueueUserWorkItem是一個靜態方法,因此可以由ThreadPool類直接用。
2.ThreadPool類的UnsafeQueueUserWorkItem方法
該方法注冊一個等待WaitHandle的委托。與QueueUserWorkItem方法不同,UnsafeQueueUserWorkItem 不會將調用堆棧傳播到輔助線程。這使得代碼可以失去調用堆棧,從而提升它的安全特權。語法如下:
[SecurityPermissionAttribute(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.NoFlags|SecurityPermissionFlag.ControlEvidence|SecurityPermissionFlag.ControlPolicy)] public static bool UnsafeQueueUserWorkItem(WaitCallback callBack,Object state)
參數說明:
callBack :System.Threading.WaitCallback類型,一個WaitCallback,表示當線程池中的線程選擇工作項時調用的委托。
state:Object類型,在接受線程池服務時傳遞給委托的對象。
返回值:Boolean類型,如果方法成功,則為true;如果無法將該工作項排隊,則引發OutOfMemoryException。
在使用 UnsafeQueueUserWorkItem方法時,可能會無意中打開一個安全漏洞。代碼訪問安全性的權限檢查基於所有調用方對堆棧的權限進行。如果使用 UnsafeQueueUserWorkItem 將工作排在某個線程池線程上,則該線程池線程的堆棧將不會具有實際調用方的上下文。惡意代碼可能會利用這一點避開權限檢查。
示例 線程池的應用
本示例主要講解一下如何用UnsafeQueueUserWorkItem方法對線程池進行操作。代碼如下:
namespace _01_05 { class Program { static void Main(string[] args) { //注冊一個等待WaitHandle的委托 ThreadPool.UnsafeQueueUserWorkItem(new WaitCallback(ThreadProc), "First task"); Console.WriteLine("將主線程掛起"); Thread.Sleep(1000);//掛走主線程 Console.WriteLine("執行主線程"); Console.ReadLine(); } //自定義方法,主要用於線程池的調用方法 public static void ThreadProc(object state) { Console.WriteLine("執行線程池 "+state.ToString()); } } }
運行結果如下圖所示。
與QueueUserWorkItem方法不同,UnsafeQueueUserWorkItem不會將調用堆棧傳播到輔助線程。這使得代碼可以失去調用堆棧,從而提升它的安全特權。
3.ThreadPool類的RegisterWaitForSingleObject方法
可以使用 ThreadPool類的RegisterWaitForSingleObject方法注冊正在等待WaitHandle的委托。該方法一共有4個語法形式,下面分別對其進行介紹。
l 語法1
注冊一個等待WaitHandle的委托,並指定一個32位有符號整數來表示超時值(以毫秒為單位)。其語法如下:
public static RegisteredWaitHandle RegisterWaitForSingleObject(WaitHandle waitObject,WaitOrTimerCallback callBack,Object state,int millisecondsTimeOutInterval,bool executeOnlyOnce)
waitObject:WaitHandle類型,要注冊的WaitHandle。使用WaitHandle而非Mutex。
CallBack:WaitOrTimerCallback類型,waitObject參數終止時調用的WaitOrTimerCallback委托。
State:Object類型,傳遞給委托的對象。
MillisecondsTimeOutInterval:Int32類型,以毫秒為單位的超時。如果millisecondsTimeOutInterval參數為0(零),函數將測試對象的狀態並立即返回。如果millisecondsTimeOutInterval為-1,則函數的超時間隔永遠不過期。
ExecuteOnlyOnce:Boolean類型,如果為true,表示在調用了委托后,線程將不再在waitObject 參數上等待;如果為false,表示每次完成等待操作后都重置計時器,直到注銷等待。
返回值:System.Threading.RegisteredWaitHandle類型,封裝本機句柄的 RegisteredWaitHandle。
l 語法2
注冊一個等待WaitHandle的委托,並指定一個64位有符號整數來表示超時值(以毫秒為單位)。其語法如下:
public static RegisteredWaitHandle RegisterWaitForSingleObject(WaitHandle waitObject,WaitOrTimerCallback callBack,Object state,long millisecondsTimeOutInterval,bool executeOnlyOnce)
參數說明:
waitObject:WaitHandle類型,要注冊的WaitHandle。使用WaitHandle而非Mutex。
CallBack:WaitOrTimerCallback類型,waitObject參數終止時調用的WaitOrTimerCallback委托。
State:Object類型,傳遞給委托的對象。
millisecondsTimeOutInterval:Int64類型,以毫秒為單位的超時。如果millisecondsTimeOutInterval參數為0(零),函數將測試對象的狀態並立即返回。如果millisecondsTimeOutInterval為-1,則函數的超時間隔永遠不過期。
ExecuteOnlyOnce:Boolean類型,如果為true,表示在調用了委托后,線程將不再在waitObject 參數上等待;如果為false,表示每次完成等待操作后都重置計時器,直到注銷等待。
返回值:System.Threading.RegisteredWaitHandle類型,封裝本機句柄的 RegisteredWaitHandle。
l 語法3
注冊一個等待WaitHandle的委托,並指定一個TimeSpan值來表示超時時間。其語法如下:
public static RegisteredWaitHandle RegisterWaitForSingleObject(WaitHandle waitObject,WaitOrTimerCallback callBack,Object state,TimeSpan timeout,bool xecuteOnlyOnce)
參數說明:
waitObject:WaitHandle類型,要注冊的WaitHandle。使用WaitHandle而非Mutex。
CallBack:WaitOrTimerCallback類型,waitObject參數終止時調用的WaitOrTimerCallback委托。
State:Object類型,傳遞給委托的對象。
Timeout:TimeSpan類型,TimeSpan表示的超時時間。如果timeout為0(零),則函數將測試對象的狀態並立即返回。如果timeout為-1,則函數的超時間隔永遠不過期。
ExecuteOnlyOnce:Boolean類型,如果為true,表示在調用了委托后,線程將不再在waitObject 參數上等待;如果為false,表示每次完成等待操作后都重置計時器,直到注銷等待。
返回值:System.Threading.RegisteredWaitHandle類型,封裝本機句柄的 RegisteredWaitHandle。
l 語法4
指定表示超時(以毫秒為單位)的32位無符號整數,注冊一個委托等待WaitHandle。其語法如下:
[CLSCompliantAttribute(false)] public static RegisteredWaitHandle RegisterWaitForSingleObject(WaitHandle waitObject,WaitOrTimerCallback callBack,Object state,uint millisecondsTimeOutInterval,bool executeOnlyOnce)
參數說明:
waitObject:WaitHandle類型,要注冊的WaitHandle。使用WaitHandle而非Mutex。
CallBack:WaitOrTimerCallback類型,waitObject參數終止時調用的WaitOrTimerCallback委托。
State:Object類型,傳遞給委托的對象。
MillisecondsTimeOutInterval:UInt32類型,以毫秒為單位的超時。如果millisecondsTimeOutInterval參數為0(零),函數將測試對象的狀態並立即返回。如果millisecondsTimeOutInterval為-1,則函數的超時間隔永遠不過期。
ExecuteOnlyOnce:Boolean類型,如果為true,表示在調用了委托后,線程將不再在waitObject參數上等待;如果為false,表示每次完成等待操作后都重置計時器,直到注銷等待。
返回值:System.Threading.RegisteredWaitHandle類型,封裝本機句柄的 RegisteredWaitHandle。
注意:在以上的4個語法中,對waitObject應用Mutex不會導致回調互斥,因為它是基於Win32 API使用默認的WT_EXECUTEDEFAULT標志,所以每次回調都在單獨的線程池線程上調度。因此,請盡可能的不要使用Mutex,應該使用最大計數為1的Semaphore。
RegisterWaitForSingleObject方法將指定的委托排隊到線程池。當發生以下兩種情況時,輔助線程將執行委托:
l 指定對象處於終止狀態。
l 超時間隔已過期。
RegisterWaitForSingleObject方法檢查指定對象的WaitHandle的當前狀態。如果對象狀態為非終止狀態,則此方法將注冊一個等待操作,該等待操作由線程池中的一個線程來執行。當對象狀態變為終止或超時間隔已過期時,委托由輔助線程執行。如果 timeOutInterval參數不為0(零),並且executeOnlyOnce參數為false,則每當事件收到信號或超時間隔過期時都會重置計時器。若要取消等待操作,請調用RegisteredWaitHandle.Unregister方法。
等待線程使用Win32的WaitForMultipleObjects函數來監視已注冊的等待操作。因此,如果必須在對RegisterWaitForSingleObject的多次調用中使用相同的本機操作系統句柄,則必須使用Win32的DuplicateHandle函數重復該句柄。這里要注意的是,不應為傳遞到RegisterWaitForSingleObject的事件對象發出脈沖,這是因為等待線程在重置前可能不會檢測到該事件已終止。
返回前,函數將修改某些類型的同步對象的狀態。修改僅發生在其終止狀態滿足等待條件的對象上。例如,信號量計數減少一。
下面通過一個簡單的例子,來說明一下如何使用RegisterWaitForSingleObject方法對線程池進行相應的操作。代碼如下:
示例 線程池的應用
本示例通過注冊正在等待的WaitHandle委托,對線程池進行操作。代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace _01_04 { class Program { public class TaskInfo//定義一個類 { public RegisteredWaitHandle Handle = null;//定義一個句柄變量 public string OtherInfo = "default";//定義一個字符串 } static void Main(string[] args) { AutoResetEvent ev = new AutoResetEvent(false);//實例化等待線程已發生的事件,將初始狀態設置為非終止 TaskInfo ti = new TaskInfo();//實例化類 ti.OtherInfo = "First task";//記錄文本信息 ti.Handle = ThreadPool.RegisterWaitForSingleObject( ev,//要進行注冊的WaitHandle類型 new WaitOrTimerCallback(WaitProc),//當WaitHandle超時或終止時要調用的方法 ti,//傳遞給委托對象的值 1000,//設置超時間隔 false//表示每次完成等待操作后都重置計時器,直到注銷等待 );//注冊一個等待的委托 Thread.Sleep(3100);//將主線程掛起 Console.WriteLine("執行主線程"); ev.Set();//將WaitHandle設置為終止 Console.WriteLine("將等待線程已發生的事件設置為終止"); Console.ReadLine(); } //state:一個對象,包含回調方法在每次執行時要使用的信息 //timedOut:如果 WaitHandle 超時,則為 true;如果其終止,則為 false。 public static void WaitProc(object state, bool timedOut) { TaskInfo ti = (TaskInfo)state; if (!timedOut)//當WaitHandle為終止時 { if (ti.Handle != null)//如果委托對象的句柄不為空 ti.Handle.Unregister(null);//取消RegisterWaitForSingleObject方法所發出的已注冊等待操作 } string StrTime = timedOut ? " WaitHandle 超時" : "終止"; Console.WriteLine("當前執行所使用的信息:" + state.ToString() + "; " + StrTime + "; 傳遞給委托的對象:" + ti.OtherInfo); } } }
運行結果如下圖所示。
圖 RegisterWaitForSingleObject方法的應用
在上圖可以看出,在上例並沒有使用for等循環語句執行線程池(ThreadPool)中的RegisterWaitForSingleObject方法,但它卻執行了3次,這是為什么呢?其主要原因是將RegisterWaitForSingleObject方法中的ExecuteOnlyOnce參數設置為false(如果將其設置為true,調用了委托后,線程將不在waitObject參數上等待,也就是只執行一次),它表示每次完成等待操作后都重置計時器,直到取消由RegisterWaitForSingleObject方法發出的已經注冊等待的操作,主要是用State參數來實現的,為了可以在該參數中記錄所傳遞的委托對象,事先必須要定義一個類,在類中定義兩個全局變量,用於記錄委托的信息,以及本機的句柄。其類的定義如下:
public class TaskInfo//定義一個類 { public RegisteredWaitHandle Handle = null;//定義一個句柄變量 public string OtherInfo = "default";//定義一個字符串 }
然后通過自定義類的Handle變量記錄本機句柄,用OtherInfo變量記錄委托信息,如果想要取消RegisterWaitForSingleObject方法所發出的已注冊的等待操作,可以用Handle變量的Unregister(null)方法取消注冊。
為了讀者能更好的定義RegisterWaitForSingleObject方法所執行的自定義事件,下面對RegisterWaitForSingleObject方法中CallBack參數所調用的方法進行說明,CallBack參數是WaitHandle類型的,主要是當WaitHandle超時或終止時所調用的方法,其語法結構為:
[ComVisibleAttribute(true)] public delegate void WaitOrTimerCallback(Object state,bool timedOut)
參數說明:
state:Object類型,一個對象,包含回調方法在每次執行時要使用的信息。
TimedOut:Boolean類型,如果WaitHandle超時,則為true;如果其終止,則為false。
通過以上語法,用戶可自定義一個方法,用於線程池的調用,代碼如下:
public static void WaitProc(object state, bool timedOut) { }