1. 程式人生 > >20181105_執行緒之Task

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)         使用TaskFactoryAsyncState來明確哪一個執行緒最先完成任務  

 //不能使用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)         DelaySleep  

{
                    //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; 使用forforeach 

//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)   Parallelbreakstop, 不推薦使用

 

//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")}");