在上一篇C#多線程之線程池篇1中,我們主要學習瞭如何線上程池中調用委托以及如何線上程池中執行非同步操作,在這篇中,我們將學習線程池和並行度、實現取消選項的相關知識。 三、線程池和並行度 在這一小節中,我們將學習對於大量的非同步操作,使用線程池和分別使用單獨的線程在性能上有什麼差異性。具體操作步驟如下: ...
在上一篇C#多線程之線程池篇1中,我們主要學習瞭如何線上程池中調用委托以及如何線上程池中執行非同步操作,在這篇中,我們將學習線程池和並行度、實現取消選項的相關知識。
三、線程池和並行度
在這一小節中,我們將學習對於大量的非同步操作,使用線程池和分別使用單獨的線程在性能上有什麼差異性。具體操作步驟如下:
1、使用Visual Studio 2015創建一個新的控制台應用程式。
2、雙擊打開“Program.cs”文件,編寫代碼如下所示:
1 using System; 2 using System.Diagnostics; 3 using System.Threading; 4 using static System.Console; 5 using static System.Threading.Thread; 6 7 namespace Recipe03 8 { 9 class Program 10 { 11 static void UseThreads(int numberOfOperations) 12 { 13 using(var countdown=new CountdownEvent(numberOfOperations)) 14 { 15 WriteLine("Scheduling work by creating threads"); 16 for(int i = 0; i < numberOfOperations; i++) 17 { 18 var thread = new Thread(() => 19 { 20 Write($"{CurrentThread.ManagedThreadId},"); 21 Sleep(100); 22 countdown.Signal(); 23 }); 24 thread.Start(); 25 } 26 countdown.Wait(); 27 WriteLine(); 28 } 29 } 30 31 static void UseThreadPool(int numberOfOperations) 32 { 33 using(var countdown=new CountdownEvent(numberOfOperations)) 34 { 35 WriteLine("Starting work on a threadpool"); 36 for(int i = 0; i < numberOfOperations; i++) 37 { 38 ThreadPool.QueueUserWorkItem(_ => 39 { 40 Write($"{CurrentThread.ManagedThreadId},"); 41 Sleep(100); 42 countdown.Signal(); 43 }); 44 } 45 countdown.Wait(); 46 WriteLine(); 47 } 48 } 49 50 static void Main(string[] args) 51 { 52 const int numberOfOperations = 500; 53 var sw = new Stopwatch(); 54 sw.Start(); 55 UseThreads(numberOfOperations); 56 sw.Stop(); 57 WriteLine($"Execution time using threads: {sw.ElapsedMilliseconds}"); 58 59 sw.Reset(); 60 sw.Start(); 61 UseThreadPool(numberOfOperations); 62 sw.Stop(); 63 WriteLine($"Execution time using the thread pool: {sw.ElapsedMilliseconds}"); 64 } 65 } 66 }
3、運行該控制台應用程式,運行效果(每次運行效果可能不同)如下圖所示:
在上述代碼中,我們首先創建了500個線程來執行非同步操作,我們發現使用每個單獨的線程執行非同步操作所消耗的時間為175毫秒。然後我們使用線程池來執行500個非同步操作,我們發現所消耗的時間為9432毫秒。這說明使用線程池來執行大併發的非同步操作會節省操作系統的記憶體和線程數,但是會嚴重影響應用程式的性能。
四、實現取消選項
在這一小節中,我們將學習如何線上程池中取消一個非同步操作。具體步驟如下所示:
1、使用Visual Studio 2015創建一個新的控制台應用程式。
2、雙擊打開“Program.cs”文件,編寫代碼如下所示:
1 using System; 2 using System.Threading; 3 using static System.Console; 4 using static System.Threading.Thread; 5 6 namespace Recipe04 7 { 8 class Program 9 { 10 // CancellationToken:傳播有關應取消操作的通知。 11 static void AsyncOperation1(CancellationToken token) 12 { 13 WriteLine("Starting the first task"); 14 for (int i = 0; i < 5; i++) 15 { 16 // IsCancellationRequested:獲取是否已請求取消此標記。 17 // 如果已請求取消此標記,則為 true,否則為 false。 18 if (token.IsCancellationRequested) 19 { 20 WriteLine("The first task has been canceled."); 21 return; 22 } 23 Sleep(TimeSpan.FromSeconds(1)); 24 } 25 WriteLine("The first task has completed succesfully"); 26 } 27 28 static void AsyncOperation2(CancellationToken token) 29 { 30 try 31 { 32 WriteLine("Starting the second task"); 33 for (int i = 0; i < 5; i++) 34 { 35 // 如果已請求取消此標記,則引發 System.OperationCanceledException。 36 token.ThrowIfCancellationRequested(); 37 Sleep(TimeSpan.FromSeconds(1)); 38 } 39 WriteLine("The second task has completed succesfully"); 40 } 41 catch (OperationCanceledException) 42 { 43 WriteLine("The second task has been canceled."); 44 } 45 } 46 47 static void AsyncOperation3(CancellationToken token) 48 { 49 bool cancellationFlag = false; 50 // 註冊一個將在取消此 System.Threading.CancellationToken 時調用的委托。 51 // Register的參數是一個Action類型的委托,該委托在取消 System.Threading.CancellationToken 時執行 52 token.Register(() => cancellationFlag = true); 53 WriteLine("Starting the third task"); 54 for (int i = 0; i < 5; i++) 55 { 56 if (cancellationFlag) 57 { 58 WriteLine("The third task has been canceled."); 59 return; 60 } 61 Sleep(TimeSpan.FromSeconds(1)); 62 } 63 WriteLine("The third task has completed succesfully"); 64 } 65 66 static void Main(string[] args) 67 { 68 // CancellationTokenSource:通知 System.Threading.CancellationToken,告知其應被取消。 69 using (var cts = new CancellationTokenSource()) 70 { 71 // 獲取與此 System.Threading.CancellationTokenSource 關聯的 System.Threading.CancellationToken。 72 CancellationToken token = cts.Token; 73 ThreadPool.QueueUserWorkItem(_ => AsyncOperation1(token)); 74 Sleep(TimeSpan.FromSeconds(2)); 75 // 傳達取消請求。 76 cts.Cancel(); 77 } 78 79 // CancellationTokenSource:通知 System.Threading.CancellationToken,告知其應被取消。 80 using (var cts = new CancellationTokenSource()) 81 { 82 // 獲取與此 System.Threading.CancellationTokenSource 關聯的 System.Threading.CancellationToken。 83 CancellationToken token = cts.Token; 84 ThreadPool.QueueUserWorkItem(_ => AsyncOperation2(token)); 85 Sleep(TimeSpan.FromSeconds(2)); 86 // 傳達取消請求。 87 cts.Cancel(); 88 } 89 90 // CancellationTokenSource:通知 System.Threading.CancellationToken,告知其應被取消。 91 using (var cts = new CancellationTokenSource()) 92 { 93 // 獲取與此 System.Threading.CancellationTokenSource 關聯的 System.Threading.CancellationToken。 94 CancellationToken token = cts.Token; 95 ThreadPool.QueueUserWorkItem(_ => AsyncOperation3(token)); 96 Sleep(TimeSpan.FromSeconds(2)); 97 // 傳達取消請求。 98 cts.Cancel(); 99 } 100 101 Sleep(TimeSpan.FromSeconds(2)); 102 } 103 } 104 }
3、運行該控制台應用程式,運行效果如下圖所示:
在上述代碼中,我們使用了CancellationTokenSource和CancellationToken類,這兩個類在.NET 4.0引入,現在已經成為取消非同步操作事實上的標準。
在“AsyncOperation1”方法中,我們僅僅是輪詢檢查“CancellationToken.IsCancellationRequested”屬性,如果該屬性為true,這意味著我們的操作已被取消,我們必須放棄此次操作。
在“AsyncOperation2”方法中,我們調用CancellationToken的“ThrowIfCancellationRequested”方法來檢查操作是否已被取消,如果已被取消,該方法會拋出OperationCanceledException異常,我們使用try/catch塊捕獲這個異常來中止非同步操作的執行。
在“AsyncOperation3”方法中,我們調用CancellationToken的“Register”方法來註冊一個非同步操作被取消時被調用的回調方法。這種方式可以允許我們將取消操作的邏輯鏈接到另一個非同步操作中。