20181105_執行緒之Task
Task是基於.net Framework3.0框架, Task使用的執行緒也是來自於ThreadPool
多執行緒的兩個意義: 優化體驗(常見於不卡介面), 提升執行速度(不同執行緒可以分擔運算任務)
總結:
//Task6個方法: WaitAll Task.WaitAny(taskList.ToArray());//會阻塞當前執行緒,等著某個任務完成後,才進入下一行 卡介面; 有好幾個過載, 超時. . . WaitAny //Task.WaitAny(taskList.ToArray());//會阻塞當前執行緒,等著[某個任務]完成後(只要有一個完成, 就會進入下一行程式碼),才進入下一行 卡介面 應用場景 //一個業務查詢操作有多個數據源 首頁--多執行緒併發--拿到全部資料後才能返回 WaitAll //一個商品搜素操作有多個數據源,商品搜尋--多個數據源--多執行緒併發--只需要一個結果即可-- WhenAll 這個WhenAll啥也沒有做, 就是表示當所有執行緒都把事情做完之後,然後再去做什麼; 他必須要和ContinuWith一起使用, 否則便失去意義; Task.WhenAll(taskList).ContinueWith(()={"一起去吃飯"});翻譯一下就是, 當tasklist中所有執行緒都完成工作了, 那麼繼續執行 一起去吃飯 WhenAny 這個和WhenAll一樣. 但是指的是任意一個執行緒完成 ContinueWith ContinueWhenAll taskFactory.ContinueWhenAll 在taskFactory中也有類似的WhenAll 和 WhenAny //Task.Delay(1000);//延遲 不會卡 //Thread.Sleep(1000);//等待 卡
一. Task.Run→執行緒的啟動是基於非同步形式, 下面程式碼演示使用Task/new Task() / TaskFactory方式來啟動執行緒:
Task.Run(() => this.DoSomethingLong("btnTask_Click1"));//Task的Run在.net 4.5 的時候才有 TaskFactory taskFactory = Task.Factory;//從.net 4.0開始才有 taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click3")); //利用TaskFactroy來啟動一個新的執行緒 Task t = new Task(() => { Console.WriteLine("opasdfawerfwe"); }); t.Start();
二. 使用task實現等待當前執行緒完成, WaitAll和WaitAny, 然後繼續後面的任務(卡介面):
////什麼時候用多執行緒? 任務能併發執行;提升速度;優化體驗 List<Task> taskList = new List<Task>(); Console.WriteLine($"專案經理啟動一個專案。。【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】"); taskList.Add(Task.Run(() => this.Coding("悟空", "Client")));//做客戶端 taskList.Add(Task.Run(() => this.Coding("八戒", "Portal")));//做門戶(後臺) taskList.Add(Task.Run(() => this.Coding("唐僧", "Service")));//做服務 taskList.Add(Task.Run(() => this.Coding("如來", "Jump"))); //做專案跳轉 taskList.Add(Task.Run(() => this.Coding("沙僧", "Monitor")));//做監控系統 ////阻塞:需要完成後再繼續, 誰執行下面的Task, 就會阻塞誰 Task.WaitAll(taskList.ToArray());//會阻塞當前執行緒,等著 [全部任務] 完成後,才進入下一行 卡介面 Task.WaitAny(taskList.ToArray());//會阻塞當前執行緒,等著 [某個任務] 完成後(只要有一個完成, 就會進入下一行程式碼),才進入下一行 卡介面,多執行緒加快速度,但是全部任務完成後,才能執行的操作 Task.WaitAny(taskList.ToArray(), 1000); //限時等待 Console.WriteLine($"告訴甲方驗收,上線使用【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
小結:
waitAll→一個業務查詢操作有多個數據源 首頁--多執行緒併發--拿到全部資料後才能返回 WaitAll WaitAny→一個商品搜素操作有多個數據源,商品搜尋--多個數據源--多執行緒併發--只需要一個結果即可--WaitAny
三. 使用WaitAll或WaitAny即等待任務完成, 又不卡UI的方法, 包一層
Task.Run( //再包一層Task, 就是避開當前的UI執行緒來執行運算方法 () => { //多執行緒加快速度,但是全部任務完成後,才能執行的操作 Task.WaitAll(taskList.ToArray());//會阻塞當前執行緒,等著全部任務完成後,才進入下一行 卡介面 Console.WriteLine($"告訴甲方驗收,上線使用【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
四. 使用WhenAll 和 WhenAny, 來實現即不卡介面, 又可以控制執行順序(等待所有任務完成, 才執行後續任務)
//當任何一個(WhenAny)執行緒完成任務, 就繼續執行(ContinueWith)後面的動作 Task.WhenAny(taskList.ToArray()).ContinueWith(t => { Console.WriteLine(taskList.ToArray().FirstOrDefault(s => s.Status == TaskStatus.RanToCompletion)); Console.WriteLine($"風騷的笑. . .【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】"); }) ; //當所有(WhenAll)執行緒完成任務, 就繼續執行(ContinueWith)後面的動作 Task.WhenAll(taskList.ToArray()).ContinueWith(t => { Console.WriteLine($"部署環境,聯調測試。。。【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】"); }) ;
五. 實戰:
a) Task控制執行緒數量控制, 完成1000個任務, 但是最多隻能啟動11個執行緒, 程式碼如下:
//Task控制執行緒數量 List<int> list = new List<int>(); for (int i = 0; i < 10000; i++) { list.Add(i); } //專案規定完成10000個任務 但是最多隻能有11個執行緒 Action<int> action = i => { Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString("00")); Console.WriteLine($"當前i的值是→{ i.ToString("00")}"); Thread.Sleep(new Random(i).Next(100, 300)); }; List<Task> taskList = new List<Task>(); //將任務從list中取出來 foreach (var i in list) { int k = i; taskList.Add(Task.Run(() => action.Invoke(k))); //每建立一個任務, 就將任務新增到itasklist中; 這裡是非同步執行的 if (taskList.Count > 10)//檢查是否大於10, 就開始等待; 注意如果這樣判斷的話, 其實當前用了11個執行緒 { //WaitAny 也是卡介面 Task.WaitAny(taskList.ToArray());//最起碼要完成一個 //這一步檢查執行緒的狀態, 如果執行緒的狀態不等於 已完成 的, 則繼續保留在taskList中 taskList = taskList.Where(t => t.Status != TaskStatus.RanToCompletion).ToList(); //var v = taskList.Max(m => m.Id); //Console.Write(v + ".................... ................."); } } //等待所有執行緒完成後, 開始後續的任務 Task.WhenAll(taskList.ToArray());
b) 使用TaskFactory的AsyncState來明確哪一個執行緒最先完成任務
//不能使用Task啟動,使用TaskFactory是可以的, 可以使用t.AsyncState狀態來標識 TaskFactory taskFactory1 = new TaskFactory(); List<Task> taskList = new List<Task>(); taskList.Add(taskFactory1.StartNew(o => this.Coding("悟空", "Client"), "悟空")); taskList.Add(taskFactory1.StartNew(o => this.Coding("八戒", "Portal"), "八戒")); taskList.Add(taskFactory1.StartNew(o => this.Coding("沙僧", "Service"), "沙僧")); //只要有任何一個人完成任務之後, 就獲取整個人的編號 taskFactory1.ContinueWhenAny(taskList.ToArray(), t => { Console.WriteLine(t.AsyncState); Console.WriteLine($"部署環境,聯調測試。。。【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】"); }); //當所有的人都完成任務之後, 獲取第一個完成任務的人 taskFactory1.ContinueWhenAll(taskList.ToArray(), tList => { Console.WriteLine(tList[0].AsyncState); Console.WriteLine($"部署環境,聯調測試。。。【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】"); });
c) Delay和Sleep
{ //Task.Delay(1000);//延遲 不會卡 ; 延遲1秒鐘後, 執行後續動作 //Thread.Sleep(1000);//等待 卡 Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); Thread.Sleep(2000); stopwatch.Stop(); Console.WriteLine(stopwatch.ElapsedMilliseconds); } { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); //不卡介面; 然後會等待2000毫秒後去執行continueWith中的函式; Task.Delay(2000).ContinueWith(t => { stopwatch.Stop(); Console.WriteLine(stopwatch.ElapsedMilliseconds); }); } { //使用Task.Run將Thread.Sleep包一層 同樣可以實現不卡介面, 延遲執行 //Stopwatch stopwatch = new Stopwatch(); //stopwatch.Start(); //Task.Run(() => //{ // Thread.Sleep(2000); // stopwatch.Stop(); // Console.WriteLine(stopwatch.ElapsedMilliseconds); //}); }
d) Task指定回撥 ; ContinueWith
Task.Run(() => this.Coding("悟空", "Client")).ContinueWith(t => { });
六. 使用Parallel並行程式設計, 在Task的基礎上做了封裝, 從.net Framework4.0開始
a) 使用Parallel並行程式設計完成一個多執行緒啟動; 注意這個會卡介面; 因為主執行緒參與了計算
//當然這個也可以使用Task來完成 Parallel.Invoke(() => this.Coding("悟空", "Client") , () => this.Coding("八戒", "Portal") , () => this.Coding("沙僧", "Service"));
b) 一些簡單API; 使用for和foreach
//for迴圈的操作; 啟動5個執行緒; 0表示for的開始, 5表示結束; 簡化的for Parallel.For(0, 5, i => this.Coding("悟空", "Client" + i)); //使用foreach; 對集合中的每一個元素都執行後面的Action Parallel.ForEach(new string[] { "0", "1", "2", "3", "4" }, i => this.Coding("悟空", "Client" + i));
c) 使用 parallel來控制當前最大的執行緒數量; 卡介面
//parallelOptions 可以控制併發數量;控制執行緒數量; ParallelOptions parallelOptions = new ParallelOptions(); parallelOptions.MaxDegreeOfParallelism = 3; //任意時刻只有三個執行緒在執行 Parallel.For(0, 10, parallelOptions, i => this.Coding("悟空", "Client" + i));
d) 使用 parallel來控制當前最大的執行緒數量; 不卡介面
Task.Run(() => { ParallelOptions parallelOptions = new ParallelOptions(); parallelOptions.MaxDegreeOfParallelism = 3; Parallel.For(0, 10, parallelOptions, i => this.Coding("悟空", "Client" + i));
});
e) Parallel的break和stop, 不推薦使用
//Break Stop 都不推薦用 ParallelOptions parallelOptions = new ParallelOptions(); parallelOptions.MaxDegreeOfParallelism = 3; Parallel.For(0, 40, parallelOptions, (i, state) => { if (i == 2) { Console.WriteLine($"執行緒Break,當前任務結束 i={i} {Thread.CurrentThread.ManagedThreadId.ToString("00")}"); state.Break();//結束Parallel當次的這一次操作 結束了當前的執行緒的操作, 如果當前執行緒操作了很多i , 那麼這個執行緒所操作的i就不會輸出了; 但是如果當前執行緒如果是主執行緒的話, 那麼它會直接結束Parallel的, 這要看運氣了. . . return;//必須帶上 } //if (i == 20) //{ // Console.WriteLine($"執行緒Stop,Parallel結束 i={i} {Thread.CurrentThread.ManagedThreadId.ToString("00")}"); // state.Stop();//結束Parallel全部操作 等於break // return;//必須帶上 //} this.Coding("悟空", "Client" + i); }); //Break 實際上結束了當前這個執行緒;如果是主執行緒,等於Parallel都結束了 //多執行緒的終止本身就不靠譜
Task.Run( //再包一層Task, 就是避開當前的UI執行緒來執行運算方法
() =>
{
//多執行緒加快速度,但是全部任務完成後,才能執行的操作
Task.WaitAll(taskList.ToArray());//會阻塞當前執行緒,等著全部任務完成後,才進入下一行 卡介面
Console.WriteLine($"告訴甲方驗收,上線使用【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");