在多線程編程中,線程的建立和銷燬是很是消耗系統資源的,所以,C#引入了池的概念,相似的還有數據庫鏈接池,這樣,維護一個池,池內維護的一些線程,須要的時候從池中取出來,不須要的時候放回去,這樣就避免了重複建立和銷燬線程。算法
ThreadPool類 MSDN幫助信息: http://msdn.microsoft.com/zh-cn/library/system.threading.threadpool.aspx#Y0數據庫
將任務添加進線程池:編程
ThreadPool.QueueUserWorkItem(new WaitCallback((方法名)); ThreadPool.QueueUserWorkItem(new WaitCallback((方法名),傳入方法的參數);
對線程池的線程數量進行控制多線程
SetMaxThreads(Int32, Int32) //設置能夠同時處於活動狀態的線程池的請求數目。全部大於此數目的請求將保持排隊狀態,直到線程池線程變爲可用。 SetMinThreads(Int32, Int32) //發出新的請求時,在切換到管理線程建立和銷燬的算法以前設置線程池按需建立的線程的最小數量。
對線程池線程數量控制的驗證函數
public static void Main() { ThreadPool.SetMinThreads(1, 1); ThreadPool.SetMaxThreads(1, 1); for (int i = 0; i < 10; i++) { ThreadPool.QueueUserWorkItem(new WaitCallback((object obj) => { Console.WriteLine("當前值爲:" + obj + "線程ID" + Thread.CurrentThread.ManagedThreadId); }),i); } Console.Read(); }
最大線程數量和最小線程數量所有設置爲1,上述代碼的執行結果爲:測試
能夠看到只開啓了一個線程。將最大線程改成2spa
public static void Main() { ThreadPool.SetMinThreads(1, 1); ThreadPool.SetMaxThreads(2, 2); for (int i = 0; i < 10; i++) { ThreadPool.QueueUserWorkItem(new WaitCallback((object obj) => { Console.WriteLine("當前值爲:" + obj + "線程ID" + Thread.CurrentThread.ManagedThreadId); }),i); } Console.Read(); }
此時啓動了兩個線程線程
可是這最多和最少並非說必定要使用這麼多線程的,好比,我設置最少10個線程,可是實際上可能只試用了3-4個,可是線程池中確實是最少會維護着10個線程,不必定每次所有都啓用的。code
public static void Main() { ThreadPool.SetMinThreads(10, 10); ThreadPool.SetMaxThreads(20, 20); Console.WriteLine("測試開始!"); for (int i = 0; i < 10; i++) { ThreadPool.QueueUserWorkItem(new WaitCallback((object obj) => { Console.WriteLine("當前值爲:" + obj + "線程ID" + Thread.CurrentThread.ManagedThreadId); }),i); } Console.WriteLine("測試結束!"); Console.Read(); }
上面的執行結果:blog
爲何打印測試結束的語句執行的這麼靠前呢?這是什麼緣由呢?
這是由於在循環中將任務添加到線程池中後,並無等待線程執行完成再繼續執行主線程,也就是線程池中的現成是如何啓動及結束咱們是不知道的,ThreadPool沒有提供簡單的方法來獲取工做線程是否已經結束,因此須要經過其餘方法實現。此時,須要引入ManualResetEvent類,MSDN:https://msdn.microsoft.com/zh-cn/library/system.threading.manualresetevent.aspx,
ManualResetEvent 容許線程經過發信號互相通訊。一般,此通訊涉及一個線程在其餘線程進行以前必須完成的任務。
當一個線程開始一個活動(此活動必須完成後,其餘線程才能開始)時,它調用 Reset 以將 ManualResetEvent 置於非終止狀態。此線程可被視爲控制 ManualResetEvent。調用 ManualResetEvent 上的 WaitOne 的線程將阻止,並等待信號。當控制線程完成活動時,它調用 Set 以發出等待線程能夠繼續進行的信號。並釋放全部等待線程。
一旦它被終止,ManualResetEvent 將保持終止狀態,直到它被手動重置。即對 WaitOne 的調用將當即返回。
能夠經過將布爾值傳遞給構造函數來控制 ManualResetEvent 的初始狀態,若是初始狀態處於終止狀態,爲 true;不然爲 false。
方法WaitOne(Timeout.Infinite, true); 阻止當前線程,直到當前 WaitHandle 收到信號爲止。
方法Set(); 將事件狀態設置爲終止狀態,容許一個或多個等待線程繼續。
這段話究竟是什麼意思呢?咱們經過一段代碼來理解
public static void Main() { ThreadPool.SetMinThreads(3,3); ManualResetEvent mre = new ManualResetEvent(false); for (int i = 0; i < 3; i++) { ThreadPool.QueueUserWorkItem(new WaitCallback((object obj) => { Console.WriteLine("線程 " + obj.ToString() + " 已啓動! 線程ID爲:" + Thread.CurrentThread.ManagedThreadId ); mre.WaitOne(); Thread.Sleep(500); Console.WriteLine("線程 " + obj.ToString() + " 已結束!線程ID爲:"
+ Thread.CurrentThread.ManagedThreadId );
}), i);
}
Console.Read();
}
上述代碼的執行結果是:
從執行結果中能夠看到,咱們往線程池中添加了三個任務,線程池啓用了三個線程去執行。當任務方法執行到mre.WaitOne();時,線程被ManualResetEvent 阻止,並無繼續往下走,也就是此時線程們正在等待一個信號,下面咱們就給線程們發出這個信號。
public static void Main() { ThreadPool.SetMinThreads(3,3); ManualResetEvent mre = new ManualResetEvent(false); for (int i = 0; i < 3; i++) { ThreadPool.QueueUserWorkItem(new WaitCallback((object obj) => { Console.WriteLine("線程 " + obj.ToString() + " 已啓動! 線程ID爲:" + Thread.CurrentThread.ManagedThreadId ); mre.WaitOne(); Thread.Sleep(500); Console.WriteLine("線程 " + obj.ToString() + " 已結束!線程ID爲:" + Thread.CurrentThread.ManagedThreadId); }), i); } if (Console.ReadLine() == "go") { mre.Set(); } Console.Read(); }
三個新線程雖然被阻止,可是主線程是能夠繼續執行的,當主線程收到用戶輸入的go命令時,給三個線程發送信號,線程們收到信號後繼續執行,並打印出執行結束的標識。
相信到這裏咱們應該可以理解WaitOne和Set的用法了,下面咱們在看看Reset方法,咱們在mre.Set()後,在開啓三個新的線程
public static void Main() { ThreadPool.SetMinThreads(3,3); ManualResetEvent mre = new ManualResetEvent(false); for (int i = 0; i < 3; i++) { ThreadPool.QueueUserWorkItem(new WaitCallback((object obj) => { Console.WriteLine("線程 " + obj.ToString() + " 已啓動! 線程ID爲:" + Thread.CurrentThread.ManagedThreadId ); mre.WaitOne(); Thread.Sleep(600); Console.WriteLine("線程 " + obj.ToString() + " 已結束!線程ID爲:" + Thread.CurrentThread.ManagedThreadId); }), i); } if (Console.ReadLine() == "go") { mre.Set(); } Thread.Sleep(1000); Console.WriteLine("*******************************再開啓三個線程***********************************"); for (int i = 3; i < 6; i++) { ThreadPool.QueueUserWorkItem(new WaitCallback((object obj) => { Console.WriteLine("線程 " + obj.ToString() + " 已啓動! 線程ID爲:" + Thread.CurrentThread.ManagedThreadId); mre.WaitOne(); Thread.Sleep(600); Console.WriteLine("線程 " + obj.ToString() + " 已結束!線程ID爲:" + Thread.CurrentThread.ManagedThreadId); }), i); } Console.Read(); }
運行代碼看看效果
新開的線程和以前開的線程的任務方法是如出一轍的啊,爲何沒有等待信號而直接繼續運行了呢?
這是由於咱們在調用ManualResetEvent的Set方法後,在調用其 Reset 方法前會一直保持終止狀態,因此,新線程任務方法中的WaitOne是無效的,由於此時ManualResetEvent是終止狀態的。下面咱們加上Reset方法看看效果
public static void Main() { ThreadPool.SetMinThreads(3,3); ManualResetEvent mre = new ManualResetEvent(false); for (int i = 0; i < 3; i++) { ThreadPool.QueueUserWorkItem(new WaitCallback((object obj) => { Console.WriteLine("線程 " + obj.ToString() + " 已啓動! 線程ID爲:" + Thread.CurrentThread.ManagedThreadId ); mre.WaitOne(); Thread.Sleep(600); Console.WriteLine("線程 " + obj.ToString() + " 已結束!線程ID爲:" + Thread.CurrentThread.ManagedThreadId); }), i); } if (Console.ReadLine() == "go") { mre.Set(); } Thread.Sleep(1000); Console.WriteLine("*******************************再開啓三個線程***********************************"); mre.Reset(); for (int i = 3; i < 6; i++) { ThreadPool.QueueUserWorkItem(new WaitCallback((object obj) => { Console.WriteLine("線程 " + obj.ToString() + " 已啓動! 線程ID爲:" + Thread.CurrentThread.ManagedThreadId); mre.WaitOne(); Thread.Sleep(600); Console.WriteLine("線程 " + obj.ToString() + " 已結束!線程ID爲:" + Thread.CurrentThread.ManagedThreadId); }), i); } if (Console.ReadLine() == "go") { mre.Set(); } Console.Read(); }
執行結果:
此時,達到了咱們想要的結果。
最後調用 mre.Close();釋放資源便可。
如今明白了ManualResetEvent類的使用,想要解決開始的「測試開始、測試結束」打印順序問題就是張飛吃豆芽了吧?!