C# 執行緒 執行緒池 Task的使用介紹
從 .NET Framework 4.5 開始,任何使用 async/await 進行修飾的方法,都會被認為是一個非同步方法;實際上,這些非同步方法都是基於佇列的執行緒任務,從你開始使用 Task 去執行一段程式碼的時候,實際上就相當於開啟了一個執行緒,預設情況下,這個執行緒數由執行緒池 ThreadPool 進行管理的。
執行緒:
Thread,由CLR提供,可以提供細粒度的執行緒控制,但是在返回值、多個執行緒並行/序列時候顯得功能不足,需要進一步封裝才行,而且執行緒的開啟、停止都需要時間開銷,亂用的話還造成浪費。
執行緒池:
ThreadPool,由CLR提供,提供粗粒度的執行緒控制,主要用來執行短時間內執行的程式碼片段,一個程序有一個執行緒池,由CLR統一排程。它對執行緒的控制力較少,而且在返回值、多個執行緒並行/序列時候顯也是功能不足。
Task,對ThreadPool和Thread的包裝,可以根據任務時間長短選擇使用執行緒池還是新的執行緒,通過進一步擴充套件,增加了返回值、多個執行緒並行/序列等功能。它的核心是一個排程器,預設是ThreadPoolTaskScheduler。Task使用的是非同步操作一個執行緒池執行緒 Task和thread很大的一個區別就是,在task中如果有一個阻塞的話,整個task就會被阻塞住,當前的執行緒ID不會改變,在thread中如果有一個阻塞的話,會去執行另外的thread,然後回來執行原來的那個thread,執行緒ID會改變為其他的ID。
ThreadPool是Thread基礎上的一個執行緒池
Task或者說TPL是一個更上層的封裝,NB之處在於continuation。continuation的意義在於:高效能的程式通常都是跑在IO邊界或者UI事件的邊界上的,TPL的continuation可以更方便的寫這種高scalability的程式碼。Task會根據一些flag,比如是不是long-running來決定底層用Thread還是ThreadPool,另外也做了些細節優化,比如同一個執行緒跑好幾個Task,比如continuation時根據情況讓CPU空轉幾毫秒來等待前置Task結束,都是為了減少CPU上下文切換。
結論:能用Task就用Task,底下都是用的Thread或者ThreadPool。但是要注意細節,比如告訴Task是不是long-running;比如儘量別Wait;再比如IO之後的continuation要儘快結束然後把執行緒還回去,有事開個Worker做,要不然會影響後面的IO,等等。
Task的使用/// <summary> /// 最簡單的使用方式 /// </summary> /// <returns></returns> [HttpGet] [Route("GetTask")] public IActionResult GetTask() { Console.ForegroundColor = ConsoleColor.Red; // 執行一個無返回值的任務 Task.Run(() => { Console.WriteLine("runing ..."); }); // 執行一個返回 int 型別結果的任務 var res1 = Task.Run<int>(() => { return 483; }); // 宣告一個任務,僅宣告,不執行 Task t = new Task(() => { Console.WriteLine("宣告"); }); Console.ResetColor(); return Ok("test"); } /// <summary> /// 使用 TaskFactory 工廠開始非同步任務 ///使用 TaskFactory 建立並運行了兩個非同步任務,同時把這兩個任務加入了任務列表 tasks 中 ///然後立即迭代此 tasks 獲取非同步任務的執行結果,使用 TaskFactory 工廠類,可以建立一組人物,然後依次執行它們 /// </summary> /// <returns></returns> [HttpGet] [Route("GetTask2")] public IActionResult GetTask2() { Console.ForegroundColor = ConsoleColor.Red; List<Task<int>> tasks = new List<Task<int>>(); TaskFactory factory = new TaskFactory(); tasks.Add(factory.StartNew<int>(() => { Console.WriteLine("t1"); return 1; })); tasks.Add(factory.StartNew<int>(() => { Console.WriteLine("t2"); return 2; })); tasks.Add(factory.StartNew<int>(() => { Console.WriteLine("t3"); return 3; })); tasks.ForEach(t => Console.WriteLine("Task:{0}", t.Result)); Console.ResetColor(); return Ok("test2"); } /// <summary> /// 處理 Task 中的異常 ///非同步任務中發生異常會導致任務丟擲 TaskCancelException 的異常,僅表示任務退出,程式應當捕獲該異常;然後,立即呼叫 Task 進行狀態判斷,獲取內部異常 上面的程式碼模擬了 Task 內部發生的異常,並捕獲了異常 ///通常情況下,推薦使用 Task 的任務狀態判斷以進行下一步的任務處理(如果需要),如果僅僅是簡單的執行一個非同步任務,直接捕獲異常即可,這裡使用了狀態判斷,如果任務已完成,則列印一則訊息:IsCompleted;很明顯,在上面的程式碼中,此 “IsCompleted” 訊息並不會被列印到控制檯 ///注意,這裡使用了 task.IsCompletedSuccessfully 而不是 task.IsCompleted,這兩者的區別在於,前者只有在任務正常執行完成,無異常,無中途退出指令的情況下才會表示已完成,而 task.IsCompleted 則僅僅表示“任務完成” /// </summary> /// <returns></returns> [HttpGet] [Route("GetTask3")] public IActionResult GetTask3() { Console.ForegroundColor = ConsoleColor.Red; var task = Task.Run(() => { Console.WriteLine("SimpleTask"); Task.Delay(1000).Wait(); throw new Exception("SimpleTask Error"); }); try { task.Wait(); } catch (Exception ex) { Console.WriteLine(ex.Message); } if (task.IsCompletedSuccessfully)//任務成功 { Console.WriteLine("IsCompletedSuccessfully"); } if (task.IsCompleted)//任務完成 { Console.WriteLine("IsCompleted"); } Console.ResetColor(); return Ok("test2"); }
執行緒的使用
Thread t = new Thread(()=> { Console.WriteLine("Starting..."); for (int i = 0; i < 10; i++) { Console.WriteLine(i); } }); t.Start(); static void Main(string[] args) { int b = 10; string c = "主執行緒"; Thread t = new Thread(()=> PrintNumbers(b,c)); t.Start(); } static void PrintNumbers(int count,string name) { for (int i = 0; i < count; i++) { Console.WriteLine("name:{0},i:{1}",name,i); } }
執行緒池的使用
class Program { static void Main(string[] args) { WaitCallback wc1 = s =>{ Console.WriteLine("執行緒ID:{0},開始執行", Thread.CurrentThread.ManagedThreadId); Stopwatch stw = new Stopwatch(); stw.Start(); long result = SumNumbers(10000000); stw.Stop(); Console.WriteLine("執行緒ID:{0},執行完成,執行結果:{1},執行用時{2},",Thread.CurrentThread.ManagedThreadId,result,stw.ElapsedMilliseconds); }; WaitCallback wc2 = s => { Console.WriteLine("執行緒ID:{0},開始執行", Thread.CurrentThread.ManagedThreadId); Stopwatch stw = new Stopwatch(); stw.Start(); long result = SumNumbers(10000000); stw.Stop(); Console.WriteLine("執行緒ID:{0},執行完成,執行結果:{1},執行用時{2},", Thread.CurrentThread.ManagedThreadId, result, stw.ElapsedMilliseconds); }; WaitCallback wc3 = s => { Console.WriteLine("執行緒ID:{0},開始執行", Thread.CurrentThread.ManagedThreadId); Stopwatch stw = new Stopwatch(); stw.Start(); long result = SumNumbers(10000000); stw.Stop(); Console.WriteLine("執行緒ID:{0},執行完成,執行結果:{1},執行用時{2},", Thread.CurrentThread.ManagedThreadId, result, stw.ElapsedMilliseconds); }; ThreadPool.QueueUserWorkItem(wc1); ThreadPool.QueueUserWorkItem(wc2); ThreadPool.QueueUserWorkItem(wc3); Console.ReadKey(); } static long SumNumbers(int count) { long sum = 0; for (int i = 0; i < count; i++) { sum += i; } Thread.Sleep(1000); return sum; } } //等待執行緒池的執行緒執行 class Program { static void Main(string[] args) { using (ManualResetEvent m1 = new ManualResetEvent(false)) using (ManualResetEvent m2 = new ManualResetEvent(false)) using (ManualResetEvent m3 = new ManualResetEvent(false)) { ThreadPool.QueueUserWorkItem( s =>{ Console.WriteLine("執行緒ID:{0},開始執行", Thread.CurrentThread.ManagedThreadId); Stopwatch stw = new Stopwatch(); stw.Start(); long result = SumNumbers(10000000); stw.Stop(); m1.Set(); Console.WriteLine("執行緒ID:{0},執行完成,執行結果:{1},執行用時{2},", Thread.CurrentThread.ManagedThreadId, result, stw.ElapsedMilliseconds); }); ThreadPool.QueueUserWorkItem( s =>{ Console.WriteLine("執行緒ID:{0},開始執行", Thread.CurrentThread.ManagedThreadId); Stopwatch stw = new Stopwatch(); stw.Start(); long result = SumNumbers(10000000); stw.Stop(); m2.Set(); Console.WriteLine("執行緒ID:{0},執行完成,執行結果:{1},執行用時{2},", Thread.CurrentThread.ManagedThreadId, result, stw.ElapsedMilliseconds); }); ThreadPool.QueueUserWorkItem( s =>{ Console.WriteLine("執行緒ID:{0},開始執行", Thread.CurrentThread.ManagedThreadId); Stopwatch stw = new Stopwatch(); stw.Start(); long result = SumNumbers(10000000); stw.Stop(); m3.Set(); Console.WriteLine("執行緒ID:{0},執行完成,執行結果:{1},執行用時{2},", Thread.CurrentThread.ManagedThreadId, result, stw.ElapsedMilliseconds); }); //等待執行緒池的執行緒執行 m1.WaitOne(); m2.WaitOne(); m3.WaitOne(); Console.WriteLine("所有執行緒執行完成"); } Console.ReadKey(); } static long SumNumbers(int count) { long sum = 0; for (int i = 0; i < count; i++) { sum += i; } Thread.Sleep(3000); return sum; } }