在C#多線程之線程池篇中,咱們將學習多線程訪問共享資源的一些通用的技術,咱們將學習到如下知識點:多線程
在前面的「C#多線程之基礎篇」以及「C#多線程之線程同步篇」中,咱們學習瞭如何建立線程以及如何使用多線程協同工做,在這一篇中,咱們將學習另一種場景,就是咱們須要建立許多花費時間很是短的異步操做來完成某些工做。咱們知道建立一個線程是很是昂貴的,所以,對於每一個花費時間很是短的異步操做都建立一個線程是不合適的。異步
咱們可使用線程池來解決以上問題,咱們能夠在線程池中分配必定數量的線程,每當咱們須要一個線程時,咱們只須要在線程池中取得一個線程便可,而不須要建立一個新的線程,當咱們使用完一個線程時,咱們僅僅須要把線程從新放入線程池中便可。async
咱們可使用System.Threading.ThreadPool類型來利用線程池。線程池由Common Language Runtime(CLR)進行管理,這意味着每個CLR只能有一個線程池實例。ThreadPool類型有一個「QueueUserWorkItem」靜態方法,這個靜態方法接收一個委託,該委託表明一個用戶自定義的異步操做。當這個方法被調用時,這個委託就進入內部隊列,這個時候,若是線程池中沒有線程,則會建立一個新的工做線程,而後將這個委託(第一個)放入隊列中。性能
若是先前的操做執行完畢後,咱們又放置了一個新的操做到線程池,那麼咱們可能會重用上一次操做的那個工做線程。若是咱們放置新的操做的時候,線程池中的線程數已達到上限,那麼新的操做會在隊列中等待,直到線程池中有可用工做線程爲止。學習
須要注意的是,咱們儘可能在線程池中放置一些須要花費較少時間既能完成的操做,而不要放置須要花費大量時間才能完成的操做,同時不要阻塞工做線程。若是不是這樣,工做線程會變得很是繁忙,以致於不能響應用戶操做,同時也會致使性能問題以及難以調試的錯誤。spa
另外,線程池中的工做線程都是後臺線程,這意味着當全部的前臺線程執行完畢後,後臺線程會被中止執行。線程
在這一篇中,咱們將學習如何使用線程池執行異步操做、如何取消一個操做以及如何防止長時間運行一個線程。調試
1、在線程池中調用委託code
在這一小節中,咱們將學習如何在線程池中異步執行一個委託。爲了演示如何在線程池中調用一個委託,執行如下操做步驟:blog
一、使用Visual Studio 2015建立一個新的控制檯應用程序。
二、雙擊打開「Program.cs」文件,編寫代碼以下所示:
1 using System; 2 using System.Threading; 3 using static System.Console; 4 using static System.Threading.Thread; 5 6 namespace Recipe01 7 { 8 class Program 9 { 10 private delegate string RunOnThreadPool(out int threadId); 11 12 private static string Test(out int threadId) 13 { 14 WriteLine("Starting..."); 15 WriteLine($"Is thread pool thread: {CurrentThread.IsThreadPoolThread}"); 16 Sleep(TimeSpan.FromSeconds(2)); 17 threadId = CurrentThread.ManagedThreadId; 18 return $"Thread pool worker thread id was : {threadId}"; 19 } 20 21 private static void Callback(IAsyncResult ar) 22 { 23 WriteLine("Starting a callback..."); 24 WriteLine($"State passed to a callback: {ar.AsyncState}"); 25 WriteLine($"Is thread pool thread: {CurrentThread.IsThreadPoolThread}"); 26 WriteLine($"Thread pool worker thread id: {CurrentThread.ManagedThreadId}"); 27 } 28 29 static void Main(string[] args) 30 { 31 int threadId = 0; 32 var t = new Thread(() => Test(out threadId)); 33 t.Start(); 34 t.Join(); 35 WriteLine($"Thread id: {threadId}"); 36 37 RunOnThreadPool poolDelegate = Test; 38 IAsyncResult r = poolDelegate.BeginInvoke(out threadId, Callback, "a delegate asynchronous call"); 39 r.AsyncWaitHandle.WaitOne(); 40 string result = poolDelegate.EndInvoke(out threadId, r); 41 WriteLine($"Thread pool worker thread id: {threadId}"); 42 WriteLine(result); 43 44 Sleep(TimeSpan.FromSeconds(2)); 45 } 46 } 47 }
三、運行該控制檯應用程序,運行效果以下圖所示:
在第32行代碼處,咱們使用老辦法建立了一個線程,而後啓動它,並等待它執行完畢。由於thread的構造方法只接收不帶返回值的委託方法,所以,咱們給它傳遞一個lambda表達式,在該表達式中咱們調用了「Test」方法。在「Test」方法中,咱們使用「Thread.CurrentThread.IsThreadPoolThread」屬性值來判斷線程是否是來自線程池。咱們還使用「CurrentThread.ManagedThreadId」屬性值打印出運行當前代碼的線程ID。
在第10行代碼處,咱們定義了一個委託,該委託表示的方法的返回值爲字符串類型,而且接收一個整型類型的輸出參數。
在第37行代碼處,咱們將Test方法賦值給poolDelegate委託,並在第38行代碼處,使用委託的「BeginInvoke」方法運行該委託指向的方法(Test)。「BeginInvoke」接收一個回調方法,該方法將在異步操做完成以後被調用。「BeginInvoke」的第三個參數是傳遞給回調方法的一個用戶自定義的狀態。一般使用這個狀態來分辨一個異步調用。咱們使用「IAsyncResult」接口來保存「BeginInvoke」方法的返回值。
「BeginInvoke」方法當即返回,這容許咱們能夠在線程池中的工做線程執行的同時,繼續執行調用「BeginInvoke」方法的線程中的下一條代碼。
在第40行代碼處,咱們可使用「BeginInvoke」方法的返回值以及對「EndInvoke」方法的調用來得到異步操做的結果。
注意,第39行代碼不是必須的,若是咱們註釋掉這一行代碼,程序仍然運行成功,這是由於「EndInvoke」方法會一直等待異步操做完成。調用「EndInvoke」方法的另外一個好處是在工做線程中任何未處理的異常都會拋給調用線程。
若是咱們註釋掉第44行代碼,回調方法「Callback」將不會被執行,這是由於主線程已經結束,全部的後臺線程都會被中止。
2、在線程池中執行異步操做
在這一小節中,咱們將學習如何在線程池中執行異步操做,具體步驟以下:
一、使用Visual Studio 2015建立一個新的控制檯應用程序。
二、雙擊打開「Program.cs」文件,編寫代碼以下所示:
1 using System; 2 using System.Threading; 3 using static System.Console; 4 using static System.Threading.Thread; 5 6 namespace Recipe02 7 { 8 class Program 9 { 10 private static void AsyncOperation(object state) 11 { 12 WriteLine($"Operation state: {state ?? "(null)"}"); 13 WriteLine($"Worker thread id: {CurrentThread.ManagedThreadId}"); 14 Sleep(TimeSpan.FromSeconds(2)); 15 } 16 17 static void Main(string[] args) 18 { 19 const int x = 1; 20 const int y = 2; 21 const string lambdaState = "lambda state 2"; 22 23 ThreadPool.QueueUserWorkItem(AsyncOperation); 24 Sleep(TimeSpan.FromSeconds(2)); 25 26 ThreadPool.QueueUserWorkItem(AsyncOperation, "async state"); 27 Sleep(TimeSpan.FromSeconds(2)); 28 29 ThreadPool.QueueUserWorkItem(state => 30 { 31 WriteLine($"Operation state: {state}"); 32 WriteLine($"Worker thread id: {CurrentThread.ManagedThreadId}"); 33 Sleep(TimeSpan.FromSeconds(2)); 34 }, "lambda state"); 35 36 ThreadPool.QueueUserWorkItem(state => 37 { 38 WriteLine($"Operation state: {x + y}, {lambdaState}"); 39 WriteLine($"Worker thread id: {CurrentThread.ManagedThreadId}"); 40 Sleep(TimeSpan.FromSeconds(2)); 41 }, "lambda state"); 42 43 Sleep(TimeSpan.FromSeconds(2)); 44 } 45 } 46 }
三、運行該控制檯應用程序,運行效果(每次運行效果可能不一樣)以下圖所示:
在第10~15行代碼處,咱們定義了一個帶有object類型參數的「AsyncOperation」方法,而後在第23行代碼處,咱們使用ThreadPool的「QueueUserWorkItem」靜態方法在線程池中執行「AsyncOperation」方法。
在第26行代碼處,咱們又一次使用了「QueueUserWorkItem」靜態方法在線程池中執行「AsyncOperation」方法,只不過此次咱們給「AsyncOperation」方法傳遞了state參數。
在第24行和第27行代碼處,咱們讓主線程阻塞2秒鐘,以重用線程池中的工做線程。若是咱們註釋掉這兩行代碼,那麼工做線程的線程ID大部分狀況先將會不同。
在第29~41行代碼中,咱們演示瞭如何使用lambda表達式來進行線程池中的異步操做,請自行分析結果。