在上一篇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”方法来注册一个异步操作被取消时被调用的回调方法。这种方式可以允许我们将取消操作的逻辑链接到另一个异步操作中。