在上一篇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”方法來注冊一個異步操作被取消時被調用的回調方法。這種方式可以允許我們將取消操作的邏輯鏈接到另一個異步操作中。