1. 程式人生 > >異步線程 附屬篇

異步線程 附屬篇

結束 gui C# apply() 外部 clr 看到了 AR orm

C#中的並發編程知識

續接上片,做的進一步分析,本篇如果語述上有問題的,可以忽略

Thread部分


         public class TestThread
        {
            public void RunThread()
            {
            //線程  聲明線程時傳入方法A,A可以帶object類型的參數,沒有返回值,
            //如果有參數,start啟用的時候傳入。默認是前臺運行的,就是說在所有的線程走完後才運行結束
                Thread t1 = new Thread(() =>
                {
                    Thread.Sleep(2000);                   
                    Console.WriteLine(222);
                });
                Thread t2 = new Thread((object m) =>
                {
                    Thread.Sleep(6000);                  
                    Console.WriteLine(m);
                });
                t2.IsBackground = true;
                t2.Start(333);
                t1.Start();
                Thread.Sleep(1000);
                Console.WriteLine(111);
            }       
        ######## 測試部分 ########
            TestThread tw = new TestThread();
             tw.RunThread();
            
        結果:
        111
        222
        CLR自動關閉應用程序
        333出不來
        分析:
        Thread 默認是前臺運行,前臺運行完 CLR自動結束(有的不是,比如說winForm是一直運行著的,如果控制臺,比如Console.Read(),保持前臺在等待著,也是OK的)
        t1 前臺運行  t2後臺運行,他們和主線程M一起並行運行
        M         t2         t1        
        停1s      停2s       停6s
        輸出111
                 輸出222
        --------------------------CLR結束
                         輸出333
        ##########################
        //線程池初始化執行方法必須帶一個object參數
        //ThreadPool默認帶一個object的參, 沒有返回值,默認是後臺運行的,就是說在只要主線程走完了,直接就完了,不管子線程走完沒
            public void RunThreadPool()
            {
                ThreadPool.QueueUserWorkItem((object o) =>
                {
                    Thread.Sleep(6000);
                    Console.WriteLine(222);
                });
                Thread.Sleep(1000);
                Console.WriteLine(111);
            }
        ######## 測試部分 ########
            TestThread tw = new TestThread();
             tw.RunThreadPool();
            
        結果:
        111
        CLR自動關閉應用程序
        222出不來
        分析:
        ThreadPool 默認是後臺運行
        ##########################
            public void RunParaller()
            {
              //Parallel是用多個線程執行循環的工具
        //Parallel是個普通順序執行語句,只是裏面開啟多個線程跑東西,Parallerl下面的語句還是需要等Parallel執行完後才能執行的
                int result = 0;
                int lockResult = 0;
                object lb = new object();
                Parallel.For(0, 3, (i) =>
                {
                    result = result + 2;
                    //lock只能lock引用類型,利用引用對象的地址唯一作為鎖,實現lock中的代碼一次只能一個線程訪問
                    //lock讓lock裏的代碼在並行時變為串行,盡量不要在parallel中用lock(lock內的操作耗時小,lock外操作耗時大時,並行還是起作用)
                    lock (lb)
                    {
                        lockResult = lockResult + 2;
                        Thread.Sleep(2000);
                        Console.WriteLine("i={0},lockResult={1}", i, lockResult);
                    }
                    Console.WriteLine("i={0},result={1}", i, result);
                });
                Console.WriteLine(11111);
            }
            TestThread tw = new TestThread();
            tw.RunParaller();
            ######## 測試部分 ########
             結果:
             i=0,lockResult=2
             i=0,result=6
             i=1,lockResult=4
             i=1,result=6
             i=2,lockResult=6
             i=2,result=6
             11111
          
             分析:
             這個栗子有點特殊啊,如果循環大了的話,result的值不知道會不會存在問題           
            ##########################
        }
        
        

可以看出,Thread多線程,傳入的是沒有返回值的方法,不涉及到各個新開的線程返回值的討論,討論的是誰先執行完誰後執行完的影響最後結構的問題,討論域是站在一個方法下,這個方法新開啟線程 討論的,接下來我們要切換到定位多線程執行返回值這塊

Task

Task是個什麽東西呢,它在線程中的定位是什麽,為啥會誕生它,先看下面的例子


        public void TaskApply()
        {
             Console.WriteLine(1111);
             var t= Task.Run(() =>
              {
                  Thread.Sleep(8000);
                  Console.WriteLine(5555);               
              });
              Console.WriteLine(2222);
              Thread.Sleep(1000);
              Console.WriteLine(3333);                                     
        }
         ######## 測試部分 ########
         TestTask tw = new TestTask();
         tw.TaskApply();
         Console.WriteLine(4444);  
           結果:
           1111
           2222  
           (停1s)
           3333
          4444
           ----CLR結束
           5555和6666不會出來   
           分析:
           可以看出Task也是後臺運行的線程,
           首先遇到Task ,相當於新運行了一個IsBackground為true的線程,Task.Run裏面也走,外面的也走,並行向下執行,外面的2222輸出後,停個1s,輸出3333,外面的走完了,繼續走方法外的,輸出4444,方法外的也走完了,這個時候TaskRun還在那Sleep,不管,結果就出來了
          
         ##########################
           從上面看,好像他和IsBackground=true的Thread運行特征好像相似點很高,我們捋一下,首先Thread是有前後臺運行的,他傳的方法可以帶一個object類型的參數,也可以不帶,沒有返回值,二ThreadPool是必須傳一個object的參數的,是後臺運行的,也沒有返回值。
           但是呢,Task和他們還有有些不填點,Task他傳的方法不能帶參數,他是後臺運行的,重點是可以返回值的,我們可以在想調動它返回值的地方去獲取,唯一需要註意的是在獲取的時候會阻塞到當前的線程,即Task中的方法和調動他的變成串執行了,當然調用值的時候,可能Task中已經執行完了也不好說。看下面的改動
         
        public void TaskApply2()
        {
             Console.WriteLine(222);
             var t= Task.Run(() =>
              {
                  Thread.Sleep(8000);
                  Console.WriteLine(555);
                  return 666;
              });
              Console.WriteLine(333);
              Thread.Sleep(1000);
              Console.WriteLine(444);
              Console.WriteLine(t.Result);
              Console.WriteLine(777);                                     
        }
         ######## 測試部分 ########
         Console.WriteLine(111);
         TestTask tw = new TestTask();
         tw.TaskApply();
         Console.WriteLine(888);
        結果:
        111
        222 
        333
        444
        555
        666
        777
        888
        分析:
           首先最外面輸出 111,然後進入方法B裏面,輸出222,然後遇到 Task,並行開始了,外面(B)輸出333,停個1s,輸出444,遇到t.Result,不好意思,得等待Task運行完才能向下執行了,這時候Task.Run裏面的8s到了輸出555,把返回值返出來,外面(B)的輸出666,接著輸出777,最後最外面輸出888
           可以看出 如果不調返回值的話,外面(B)是是不會阻塞著等待Task.Run裏面的執行完再執行的,這就是Task返回值阻塞的特性
           

當明白上面這個栗子和分析之後,我們看下面幾個簡單的就很通順了


            public void RunBackTask()
            {
                Task t = new Task(() =>
                {
                    Thread.Sleep(6000);
                    Console.WriteLine(333);
                });
                t.Start();
                Console.WriteLine(111);
                Thread.Sleep(2000);
                Console.WriteLine(222);
            }
            //外面調用 RunBackTask這個方法的時候 首先是輸出 111,停個2s,輸出 222,Task的6s還沒到,這時候外面的繼續跑,這個333輸出與否就要看外面的了
            而我們要看下面只個鬼的話
             public void RunTaskGui()
            {
                // 111  222  333 444 555 666  順序執行              
                var ff = Task.Run(() =>
                {
                    Thread.Sleep(6000);
                    Console.WriteLine(111);
                    return 333;
                }).Result;
                Console.WriteLine(222);
                Console.WriteLine(ff);
                Thread.Sleep(6000);
                Console.WriteLine(444);
            }
            外面的調RunTaskGui(),裏面的順序的走完
            依此是 111 222 333  444,因為 Task.Run()後直接調了Result,直接阻塞了,這個方法和普通的方法就一樣了 
            

從上面我們發現,我們一直在討論調用方法A裏面是怎麽執行的,外面的方法肯定是在調用方法A直線完後再直線,即使調用方法A裏面有Task的一個異步線程B,那也是新開的一個並行的線程,雖然這個線程B不阻塞他下面的語句和我們外部的方法,我們也是在調用方法B裏的走到最後的 } 後再直線我們外面下面的方法

我們現在要切換到,管你調動方法A裏面執行什麽東東,我外面調用你後,我直接往下面走,看似外面又一層異步的調用


            public int SubTask()
            {
               Thread.Sleep(1000);
                Console.WriteLine(333);
                var ff = Task.Run(() =>
                {
                    Thread.Sleep(6000);
                    Console.WriteLine(555);
                    return 666;
                });
                Console.WriteLine(444);
                Console.WriteLine(ff.Result);
               return 777;
            }
            ######## 測試部分 ########
            Console.WriteLine(111);
            var s=Task.Run(() =>
            {
                TestTask tm = new TestTask();
                return tm.SubTask();            
            });
            Console.WriteLine(222);
            Console.WriteLine(s.Result);
            Console.WriteLine(888);
                             
            結果:
            111
            222 
            333
            444
            555
            666
            777
            888
            這個我就不解釋了,應該能順下來
            

接下來看另一個鬼 async + await

async+await


             public async void RunAwaitTask()
            {
                Thread.Sleep(1000);
                Console.WriteLine(222);
                var ff= await Task.Run(() =>
                {
                    Thread.Sleep(2000);
                    Console.WriteLine(444);
                    return 666;
                });
                Console.WriteLine(555);
                Console.WriteLine(ff);
                Console.WriteLine(777);
            }
             ######## 測試部分 ########
             TestTask tm = new TestTask();
            Console.WriteLine(111);
            tm.RunAwaitTask();
            Console.WriteLine(333);
            //Thread.Sleep(6000);
            //Console.WriteLine(888);
            結果:
            111
            222
            333  --走完後 ClR可能會結束,所以下面不一定會出來
            444
            555
            666
            777
            把 
            //Thread.Sleep(6000);
            //Console.WriteLine(888);
            放開後
            結果是:
            111
            222
            333  
            444
            555
            666
            777
            888
            

它和上面我們討論的哪個最相似,找找,我們就發現了這貨,比較下


        public void TaskApply2()
        {
             Console.WriteLine(222);
             var t= Task.Run(() =>
              {
                  Thread.Sleep(8000);
                  Console.WriteLine(555);
                  return 666;
              });
              Console.WriteLine(333);
              Thread.Sleep(1000);
              Console.WriteLine(444);
              Console.WriteLine(t.Result);
              Console.WriteLine(777);                                     
        }
         ######## 測試部分 ########
         TestTask tw = new TestTask();
         Console.WriteLine(111);
         tw.TaskApply();
         Console.WriteLine(888);
         結果:
        111
        222 
        333
        444
        555
        666
        777
        888          
            

看到異同點了沒看到了沒看到了沒看到了沒,沒看到說明上面Task的思想沒定下來

我們發現調用 一個async的方法,在進去裏面一步步執行的時候,遇到await,開始並行了,這個並行現在變成了最外層的調用方法了,而不是裏面的語句

從例子看,在執行完Console.WriteLine(222);後,RunAwaitTask這個遇到了await就開始等待了,await下面的語句就阻塞,而最外層的Console.WriteLine(333);可以走了,也就是說,await 的Task運行的和最外層的一起走並行

對於TaskApply2呢,在遇到 Task後,Task下面的語句Console.WriteLine(333);開始執行了,並行的是下面的語句,而不是外面 Console.WriteLine(888);對於Console.WriteLine(888);只是在TaskApply2方法走完 } 後才走,這裏面如果把 Console.WriteLine(t.Result);去掉的話,結果又會不一樣,但中心區別已經找到了:

async+await 這個 開始並發的點是在遇到await後,外層調用它的和他的Task並行,外層的語句就開始執行了。而普通的Task不會和外層的調用有關系,只會和Task下面的語句開啟並行,外層的是在走完 } 後才執行的。

上面只是剛引入 async+await的東西,他們還有各種限制和特征,比如:

異步方法返回類型是Task<T>,Task,或者Void,所以獲取返回值只能await或者.Result,但是 async+await和async+.Result還是不一樣的


             public async void RunAwaitTask()
            {
                Thread.Sleep(1000);
                Console.WriteLine(222);
                var ff=Task.Run(() =>
                {
                    Thread.Sleep(2000);
                    Console.WriteLine(444);
                    return 666;
                }).Result;
                Console.WriteLine(555);
                Console.WriteLine(ff);
                Console.WriteLine(777);
            }
          
        ######## 測試部分 ########
        TestTask tm = new TestTask();
        Console.WriteLine(111);
        tm.RunAwaitTask();
        Console.WriteLine(333);
       
        結果:
        111
        222       
        444
        555
        666
        777
        333
        可以看出關鍵點在於await
        不解釋了
  

這只是最簡單開始,還會有各種異步嵌套。但這個最基本的區別腦子裏必須定下來,這樣遇到各個的場景就能想版本應用和實現了,也能斷定自己寫的代碼該怎麽走了,。線程牽扯到各個線程按不同的時間段一起執行,誰先執行完,誰後執行完都會造成不同的結果。所有有了指導思想就可以慢慢診斷了

異步線程 附屬篇