三、线程池与并行度
此示例是学习如何应用线程池实现大量的操作,及与创建大量线程进行工作的区别。
1. 代码如下
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; namespace ThreadPoolDemo { class Program { static void Main(string[] args) { Console.WriteLine("开始测试线程池与自创线程。。。"); const int total = 500; long millisecondes = 0; Stopwatch sw = new Stopwatch(); sw.Start(); ThreadRun(total); sw.Stop(); millisecondes = sw.ElapsedMilliseconds; decimal mom = (decimal)(Environment.WorkingSet / (1024 * 1024.0)); sw.Reset(); sw.Start(); ThreadPoolRun(total); sw.Stop(); Console.WriteLine("自创线程总耗时 {0},占用内存:{1} MB", millisecondes,mom); Console.WriteLine("线程池总耗时 {0} ,占用内存:{1} MB", sw.ElapsedMilliseconds, Environment.WorkingSet / (1024 * 1024.0)); Console.Read(); } private static void ThreadRun(int total) { using (var countdown = new CountdownEvent(total)) { Console.WriteLine("开始--自创线程。。。"); for (int i = 0; i < total; i++) { var t = new Thread(() => { Console.WriteLine("自创线程ID :{0}", Thread.CurrentThread.ManagedThreadId); Thread.Sleep(TimeSpan.FromSeconds(0.1)); countdown.Signal();//向 CountdownEvent 注册信号,同时减小 CurrentCount 的值。 }); t.Start(); } countdown.Wait(); // 阻塞当前线程,直到 CountdownEvent 的信号数量变为 0 Console.WriteLine("-----------------"); } } private static void ThreadPoolRun(int total) { using (var countdown = new CountdownEvent(total)) { Console.WriteLine("开始--线程池。。。"); for (int i = 0; i < total; i++) { ThreadPool.QueueUserWorkItem(_ => { Console.WriteLine("线程池工作线程ID :{0}", Thread.CurrentThread.ManagedThreadId); Thread.Sleep(TimeSpan.FromSeconds(0.1)); countdown.Signal();//向 CountdownEvent 注册信号,同时减小 CurrentCount 的值。 }); } countdown.Wait(); // 阻塞当前线程,直到 CountdownEvent 的信号数量变为 0 Console.WriteLine("-----------------"); } } } }
2.程序运行结果如下图。
1) 这个示例中我们自己创建了500个线程,每个线程一个操作,每个线程都阻塞100毫秒。总计耗时 11秒,消耗资源如下图。
2)我们使用线程池执行相同的500个操作。总计耗时 9秒,消耗资源如下图。
从1)与2)的比较上可以看出来,自创线程消耗的CPU资源比线程池要多。
四、 从线程池中取消操作
如果我们要从线程池中取消某个线程的操作,应该如何实现呢?本示例使用CancellationTokenSource和CancellationToken两个类来实现在取消线程池中的操作。这两个是是在net 4.0引入的。
1.示例代码
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; namespace ThreadPoolDemo { class Program { static void Main(string[] args) { Console.WriteLine("开始测试线程池中取消正在运行的线程。。。"); using (var cts = new CancellationTokenSource()) { CancellationToken token = cts.Token; ThreadPool.QueueUserWorkItem(_ => AsyncOper(token)); Thread.Sleep(TimeSpan.FromSeconds(2)); cts.Cancel();//取消线程操作 } using (var cts = new CancellationTokenSource()) { CancellationToken token = cts.Token; ThreadPool.QueueUserWorkItem(_ => AsyncOperation(token)); Thread.Sleep(TimeSpan.FromSeconds(2)); cts.Cancel();//取消线程操作 } using (var cts = new CancellationTokenSource()) { CancellationToken token = cts.Token; ThreadPool.QueueUserWorkItem(_ => AsyncOper3(token)); Thread.Sleep(TimeSpan.FromSeconds(2)); cts.Cancel();//取消线程操作 } Thread.Sleep(TimeSpan.FromSeconds(2)); Console.WriteLine("。。。。。。。。。。取消正在运行的线程结束。。。。。。"); Console.Read(); } private static void AsyncOperation(CancellationToken token) { try { Console.WriteLine("开始--线程池中的第二个工作线程。。。"); for (int i = 0; i < 5; i++) { token.ThrowIfCancellationRequested();//获取取消请求,抛出OperationCanceledException异常, Thread.Sleep(TimeSpan.FromSeconds(1)); } Console.WriteLine("-------线程池中的第二个工作线程 工作完成----------"); } catch (OperationCanceledException ex) { Console.WriteLine("使用抛出异常方法取消第二个工作线程 ID:{0},{1}", Thread.CurrentThread.ManagedThreadId,ex.Message); } } private static void AsyncOper(CancellationToken token) { Console.WriteLine("开始--线程池中的第一个工作线程。。。"); for (int i = 0; i < 5; i++) { if (token.IsCancellationRequested)//判断是否已经取消操作 { Console.WriteLine("使用轮询方法取消工作线程 ID:{0}", Thread.CurrentThread.ManagedThreadId); return; } Thread.Sleep(TimeSpan.FromSeconds(1)); } Console.WriteLine("-------线程池中的第一个工作线程 工作完成----------"); } private static void AsyncOper3(CancellationToken token) { Console.WriteLine("开始--线程池中的第三个工作线程。。。"); bool cancel = false; token.Register(()=>cancel = true); for (int i = 0; i < 5; i++) { if (cancel)//判断是否已经取消操作 { Console.WriteLine("通过注册回调函数取消第三个工作线程 ID:{0}", Thread.CurrentThread.ManagedThreadId); return; } Thread.Sleep(TimeSpan.FromSeconds(1)); } Console.WriteLine("-------线程池中的第三个工作线程 工作完成----------"); } } }
2.运行结果如下图。
本示例一共实现了三种取消线程池中操作的方式。
- 轮询检查CancellationToken.IsCancellationRequested属性,如果为true,则说明操作被取消。
- 抛出一个OperationCancellationException异常。这允许操作之外的代码来取消操作。
- 注册一个回调函数,当操作取消时,线程池将调用回调函数,这样做的好处是将取消操作逻辑传递到另一个异步操作中。