如果我們要從線程池中取消某個線程的操作,應該如何實現呢?本示例使用CancellationTokenSource和CancellationToken兩個類來實現在取消線程池中的操作。 ...
三、線程池與並行度
此示例是學習如何應用線程池實現大量的操作,及與創建大量線程進行工作的區別。
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異常。這允許操作之外的代碼來取消操作。
- 註冊一個回調函數,當操作取消時,線程池將調用回調函數,這樣做的好處是將取消操作邏輯傳遞到另一個非同步操作中。