NETCORE - TASK多線程的使用
Task是從 .NET Framework 4 開始引入的一項基於隊列的異步任務(TAP)模式,從 .NET Framework 4.5 開始,任何使用 async/await 進行修飾的方法,都會被認為是一個異步方法;實際上,這些異步方法都是基於隊列的線程任務,從你開始使用 Task 去運行一段代碼的時候,實際上就相當於開啟了一個線程,默認情況下,這個線程數由線程池 ThreadPool 進行管理的。
1. 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"); }
2. 使用 TaskFactory 工廠開始異步任務
/// <summary> /// 使用 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"); }
上面的代碼使用 TaskFactory 創建並運行了兩個異步任務,同時把這兩個任務加入了任務列表 tasks 中,然后立即迭代此 tasks 獲取異步任務的執行結果,使用 TaskFactory 工廠類,可以創建一組人物,然后依次執行它們
3. 處理 Task 中的異常
/// <summary> /// 處理 Task 中的異常 /// </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"); }
異步任務中發生異常會導致任務拋出 TaskCancelException 的異常,僅表示任務退出,程序應當捕獲該異常;然后,立即調用 Task 進行狀態判斷,獲取內部異常
上面的代碼模擬了 Task 內部發生的異常,並捕獲了異常,通常情況下,推薦使用 Task 的任務狀態判斷以進行下一步的任務處理(如果需要),如果僅僅是簡單的執行一個異步任務,直接捕獲異常即可,這里使用了狀態判斷,如果任務已完成,則打印一則消息:IsCompleted;很明顯,在上面的代碼中,此 “IsCompleted” 消息並不會被打印到控制台
注意,這里使用了 task.IsCompletedSuccessfully 而不是 task.IsCompleted,這兩者的區別在於,前者只有在任務正常執行完成,無異常,無中途退出指令的情況下才會表示已完成,而 task.IsCompleted 則僅僅表示“任務完成”
4. 同步上下文
在 WinForm/WPF 應用程序中,也常常需要在 UI 上開辟異步任務,通常情況下,窗體控件僅允許創建其的線程訪問,在沒有 Task 的時代,處理異步上下文到同步上下文是一件非常復雜的事情,在 Task 出現以后,提供了 TaskScheduler 任務調度器,讓我們可以非常方便的在異步線程中訪問 UI 線程的資源
/// <summary> /// 獲取當前線程上下文對象 /// </summary> /// <returns></returns> [HttpGet] [Route("GetTask4")] public IActionResult GetTask4() { Console.ForegroundColor = ConsoleColor.Red; SynchronizationContext.SetSynchronizationContext(new SynchronizationContext()); var UISyncContext = TaskScheduler.FromCurrentSynchronizationContext(); var t1 = Task.Factory.StartNew(() => { return 1; }); t1.ContinueWith((atnt) => { Console.WriteLine("從這里訪問 UI 線程的資源"); }, UISyncContext); Console.ResetColor(); return Ok("test2"); }
從上面的代碼可以發現,僅僅需要調用 TaskScheduler.FromCurrentSynchronizationContext() 獲得當前線程的同步上下文,然后在執行異步任務的時候傳入,即可訪問當前線程創建的 UI 資源
5. Task 的運行方式
基於 ThreadPool 線程池的方式
/// <summary> ///基於 ThreadPool 線程池的方式 /// </summary> /// <returns></returns> [HttpGet] [Route("GetTask5")] public IActionResult GetTask5() { Console.ForegroundColor = ConsoleColor.Red; var available = ThreadPool.SetMaxThreads(8, 16); Console.WriteLine("result:{0}", available); Console.ResetColor(); return Ok("test2"); }
一個異步任務總是處於隊列中,任務隊列基於先進先出的原則,最先進入隊列的任務總是最先被執行;但是,在多線程的環境下,最先執行並不意味着最先結束,意識到這一點很重要,每個任務可調度的資源和處理的進度決定了任務的完成時間。
默認情況下,所有的任務都使用 ThreadPool 的資源,當你開啟一個 Task 的時候,實際上,是由 ThreadPool 分配了一個線程,ThreadPool 的上限取決於很多方面的因素,例如虛擬內存的大小,當 Task 開啟的數量超過ThreadPool 的上限的時候,Task 將進入排隊狀態,可以手動設置 ThreadPool 的大小
上面的代碼表示設置當前程序可使用的線程池大小,但是,SetMaxThreads 的值不應該小於托管服務器的 CPU 核心數量,否則,變量 available 的值將顯示為 false,表示未成功設置線程池上限
注意:ThreadPool 上的所有線程都是后台線程,也就是說,其IsBackground屬性是true,在托管程序退出后,ThreadPool 也將會退出。
6. 長時間運行於后台的任務
/// <summary> /// 長時間運行於后台的任務 /// </summary> /// <returns></returns> [HttpGet] [Route("GetTask6")] public IActionResult GetTask6() { Console.ForegroundColor = ConsoleColor.Red; Task.Factory.StartNew(() => { Console.WriteLine("LongRunging Task"); }, TaskCreationOptions.LongRunning); Console.ResetColor(); return Ok("test2"); }
在創建 Task 的時候,我們可能需要做一些長時間運行的業務,這個時候如果使用默認的 ThreadPool 資源,在並發狀態下,這是不合適的,因為該任務總是長時間的占用線程池中的資源,導致線程池數量受限,這種情況下,可以在創建任務的時候使用指定 TaskCreationOptions.LongRunning 方式創建 Task
7.有條件的 Task
/// <summary> /// 有條件的 Task /// </summary> /// <returns></returns> [HttpGet] [Route("GetTask7")] public IActionResult GetTask7() { Console.ForegroundColor = ConsoleColor.Red; //var order1 = Task.Run<string>(() => { Console.WriteLine("Order 1 "); return "order1 result"; }); //// 匿名委托將等待 order1 執行完成后執行,並將 order1 對象作為參數傳入 //order1.ContinueWith((task) => { Console.WriteLine("order1 is completed! {0}", task.Result); }); //var t1 = Task.Run(() => { Task.Delay(1500).Wait(); Console.WriteLine("t1"); }); //var t2 = Task.Run(() => { Task.Delay(2000).Wait(); Console.WriteLine("t2"); }); //var t3 = Task.Run(() => { Task.Delay(3000).Wait(); Console.WriteLine("t3"); }); //Task.WaitAll(t1, t2, t3); //// t1,t2,t3 完成后輸出下面的消息 //Console.WriteLine("t1,t2,t3 Is Complete"); //var t4 = Task.Run(() => { Task.Delay(1500).Wait(); Console.WriteLine("t4"); }); //var t5 = Task.Run(() => { Task.Delay(2000).Wait(); Console.WriteLine("t5"); }); //var t6 = Task.Run(() => { Task.Delay(3000).Wait(); Console.WriteLine("t6"); }); //Task.WaitAny(t4, t5, t6); //// 當任意任務完成時,輸出下面的消息,目前按延遲時間計算,在 t4 完成后立即輸出下面的信息 //Console.WriteLine("t4,t5,t6 Is Complete"); //var t7 = Task.Run(() => { Task.Delay(1500).Wait(); Console.WriteLine("t7"); }); //var t8 = Task.Run(() => { Task.Delay(2000).Wait(); Console.WriteLine("t8"); }); //var t9 = Task.Run(() => { Task.Delay(3000).Wait(); Console.WriteLine("t9"); }); //var whenAll = Task.WhenAll(t7, t8, t9); //// WhenAll 不會等待,所以這里必須顯示指定等待 //whenAll.Wait(); //// 當所有任務完成時,輸出下面的消息 //Console.WriteLine("t7,t8,t9 Is Complete"); var t10 = Task.Run(() => { Task.Delay(1500).Wait(); Console.WriteLine("t10"); }); var t11 = Task.Run(() => { Task.Delay(2000).Wait(); Console.WriteLine("t11"); }); var t12 = Task.Run(() => { Task.Delay(3000).Wait(); Console.WriteLine("t12"); }); var whenAny = Task.WhenAll(t10, t11, t12); // whenAny 不會等待,所以這里必須顯示指定等待 whenAny.Wait(); // 當任意任務完成時,輸出下面的消息,目前按延遲時間計算,在 t10 完成后立即輸出下面的信息 Console.WriteLine("t10,t11,t12 Is Complete"); Console.ResetColor(); return Ok("test2"); }
Task 內部提供多種多樣的基於隊列的鏈式任務管理方法,通過使用這些快捷方式,可以讓異步隊列有序的執行,比如ContinueWith(),ContinueWhenAll(),ContinueWhenAny(),WaitAll(),WaitAny(),WhenAll(),WhenAny()
值得注意的是,當調用 WhenAll 方法時,會返回執行任務的狀態,此狀態是所有任務的統一狀態,如果執行了 3 個任務,而其中一個出錯,則返回任務狀態表示為:Faulted,如果任意任務被取消,則狀態為:Canceled;
當調用 WhenAny() 方法時,表示任意任務完成即可表示完成,此時,會返回最先完成的任務信息
注意:WhenAll 和 WhenAny 方法正常執行,無異常,無取消,則所返回的完成狀態表示為:RanToCompletion
項目:NETCORE.TASKRORD
附代碼:https://gitee.com/wuxincaicai/NETCORE.git
引用:https://www.cnblogs.com/viter/p/10201228.html