非同步程式設計:Task(二)

C#知識系列

這篇大概要講一下Task幾個用法,Task建立的方式如下

  1. task.Start
  2. Task.Factory.StartNew
  3. Task.Run
        static void Main(string[] args)
        {
            Task task1 = new Task(() => {
                Thread.Sleep(1000);
                Console.WriteLine($"hello,task1執行緒ID:{Thread.CurrentThread.ManagedThreadId}");
            });
            task1.Start();
            Task task2 = Task.Factory.StartNew(() =>
            {
                Thread.Sleep(1000);
                Console.WriteLine($"hello,task2執行緒ID:{Thread.CurrentThread.ManagedThreadId}");
            });

            Task task3 = Task.Run(() =>
            {
                Thread.Sleep(1000);
                Console.WriteLine($"hello,task3執行緒ID:{Thread.CurrentThread.ManagedThreadId}");
            });
            Console.WriteLine("主執行緒 run ing");
            Console.ReadKey();
        }


看到上方範例,會是先執行主執行緒,再去執行其他的執行緒,從這範例來看,並不會阻塞主執行緒

接者來看另一個範例

        private void button1_Click(object sender, EventArgs e)
        {
            Console.WriteLine($"****************button1_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");

            Task.Run(() => this.DoSomethingLong("btnTask_Click1"));
            Task.Run(() => this.DoSomethingLong("btnTask_Click2"));

            TaskFactory taskFactory = Task.Factory;
            taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click3"));

            new Task(() => this.DoSomethingLong("btnTask_Click4")).Start();
            Console.WriteLine($"****************button1_Click End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");

        }

        private void DoSomethingLong(string name)
        {
            Console.WriteLine($"****************DoSomethingLong Start  {name}  {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
            long lResult = 0;
            //修改前100000000  1000000000
            for (int i = 0; i < 1000; i++)
            {
                lResult += i;
            }
            Thread.Sleep(2000);

            Console.WriteLine($"****************DoSomethingLong   End  {name}  {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************");
        }


關於Task.Run  VS Task.Factory.StartNew差異

    4.0的時候支援Task.Factory.StartNew,4.5支援Task.Run,Factory.StartNew的Method其實提供很多
Method,若執行緒長時間運作則採用Task.Factory.StartNew,剩下就使用Task.Run吧

我們來用一個比較明顯的例子,把例子改寫一下

        private void Coding(string name, string projectName)
        {
            Console.WriteLine($"***Coding Start {name} {projectName}  {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***");
            long lResult = 0;
            for (int i = 0; i < 1000000000; i++)
            {
              lResult += i;
            }
            Console.WriteLine($"***Coding   End {name} {projectName} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***");
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Console.WriteLine($"****************button1_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
            List<Task> taskList = new List<Task>();
            Console.WriteLine($"專案經理啟動一個項目...【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
            Console.WriteLine($"前置作業...【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
            Console.WriteLine($"開始Coding...【{ Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
            //非同步 沒有阻塞
            taskList.Add(Task.Run(() => this.Coding("李一", "Clinent")));
            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")));
            Console.WriteLine("告訴甲方驗收,上限使用");
            Console.WriteLine($"****************button1_Click End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");

        }

怪了...我還沒開始做,怎麼能驗收完畢,案子結束阿...

接者我們加入Task.WaitAll

Task.WaitAll:它會阻塞目前執行緒,等待處理完成之後,才能進入下一行,這個部分會卡介面。


        private void button1_Click(object sender, EventArgs e)
        {
            Console.WriteLine($"****************button1_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
            List<Task> taskList = new List<Task>();
            Console.WriteLine($"專案經理啟動一個項目...【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
            Console.WriteLine($"前置作業...【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
            Console.WriteLine($"開始Coding...【{ Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
            taskList.Add(Task.Run(() => this.Coding("李一", "Clinent")));
            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")));
            //加入WaitAll
            Task.WaitAll(taskList.ToArray());  //會阻塞目前執行緒,等待全部完成後,才能進入下一行,卡界面

            Console.WriteLine("告訴甲方驗收,上限使用");
            Console.WriteLine($"****************button1_Click End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");

        }


WaitAll也有提供限時等待的方法,這樣才不會一直等下去

Task.WaitAll(taskList.ToArray(),1000);  

WaitAll使用方式:使用者查詢有多個資料來源 ,例如:首頁→可以多執行緒開發,提升查詢效能,要拿到全部資料後才能返回 WaitAll

首先我們從上面來探討,我們再用多執行緒,首先任務能夠分拆

什麼時候使用多執行緒?

任務要能夠併發執行,提升速度,優化體驗,以CPU資源換取時間



初學者使用上的問題遇到盲點的問題

1.假設一萬筆資料,這一百筆資料 建構一個執行緒,共一百個執行緒 寫入DB ,寫入DB ,因為DB只有一個,DB忙翻天,這並無助提升效能

2.1+2...+100  用執行緒...這也必無助提升效能...

接者加上WaitAny

WaitAny:
會阻塞目前執行緒,等某個任務完成後,才能進入下一行,卡界面

        private void button1_Click(object sender, EventArgs e)
        {
            Console.WriteLine($"****************button1_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
            //首先任務能分拆
            List<Task> taskList = new List<Task>();
            Console.WriteLine($"專案經理啟動一個項目...【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
            Console.WriteLine($"前置作業...【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
            Console.WriteLine($"開始Coding...【{ Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
            //非同步 沒有阻塞
            taskList.Add(Task.Run(() => this.Coding("李一", "Clinent")));
            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.WaitAny(taskList.ToArray());  //會阻塞目前執行緒,等某個任務完成後,才能進入下一行,卡界面
            Console.WriteLine($"完成里程碑...【{ Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
            Task.WaitAll(taskList.ToArray());  //會阻塞目前執行緒,等待全部完成後,才能進入下一行,卡界面

            Console.WriteLine("告訴甲方驗收,上限使用");
            Console.WriteLine($"****************button1_Click End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");

        }


 WaitAny也有提供限時等待的方法,這樣才不會一直等下去

Task.WaitAny(taskList.ToArray(),1000);  //限時等待

WaitAny使用方式:一個商品查詢有多個資料來源  商品查詢→多個資料來源→多執行緒開發,只需要一個結果即可,就可採用

反過來另外兩個WhenAny和WhenAll是不阻塞的用法,不卡介面,比對一下WaitAny和WaitAll

        private void button1_Click(object sender, EventArgs e)
        {
            Console.WriteLine($"****************button1_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
            //首先任務能分拆
            List<Task> taskList = new List<Task>();
            Console.WriteLine($"專案經理啟動一個項目...【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
            Console.WriteLine($"前置作業...【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
            Console.WriteLine($"開始Coding...【{ Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
          
            taskList.Add(Task.Run(() => this.Coding("李一", "Clinent")));
            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.WhenAny(taskList.ToArray()).ContinueWith(t => {
                Console.WriteLine($"第一個完成,該獎勵...{Thread.CurrentThread.ManagedThreadId.ToString("00")} ");
            });

            Task.WhenAll(taskList.ToArray()).ContinueWith(t => {
                Console.WriteLine($"部屬環境,整合測試...{Thread.CurrentThread.ManagedThreadId.ToString("00")} ");
            });
            //Console.WriteLine($"****************button1_Click End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
        }

 第二種用法TaskFactory,跟上方也是大同小異,不阻塞呼叫taskfactory.ContinueWhenAny、taskfactory.ContinueWhenAll

         private void button1_Click(object sender, EventArgs e)
        {
            Console.WriteLine($"****************button1_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
            //首先任務能分拆
            List<Task> taskList = new List<Task>();
            Console.WriteLine($"專案經理啟動一個項目...【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
            Console.WriteLine($"前置作業...【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
            Console.WriteLine($"開始Coding...【{ Thread.CurrentThread.ManagedThreadId.ToString("00")}】");
            //非同步 沒有阻塞
            taskList.Add(Task.Run(() => this.Coding("李一", "Clinent")));
            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.WhenAny(taskList.ToArray()).ContinueWith(t => {
            //    Console.WriteLine($"第一個完成...{Thread.CurrentThread.ManagedThreadId.ToString("00")} ");
            //});


            //Task.WhenAll(taskList.ToArray()).ContinueWith(t => {
            //    Console.WriteLine($"部屬環境,整合測試...{Thread.CurrentThread.ManagedThreadId.ToString("00")} ");
            //});
            //第二種用法
            TaskFactory taskfactory = new TaskFactory();
            taskfactory.ContinueWhenAny(taskList.ToArray(), tList => {
                Console.WriteLine($"第一個完成...{Thread.CurrentThread.ManagedThreadId.ToString("00")} ");
            });


            taskfactory.ContinueWhenAll(taskList.ToArray(), tList => {
                Console.WriteLine($"部屬環境,整合測試...{Thread.CurrentThread.ManagedThreadId.ToString("00")} ");
            });
            Console.WriteLine($"****************button1_Click End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
        }

 

元哥的筆記