net 異步與同步


 

一、摘論

 為什么不是摘要呢?其實這個是我個人的想法,其實很多人在談論異步與同步的時候都忽略了,同步異步不是軟件的原理,其本身是計算機的原理及概念,這里就不過多的闡述計算機原理了。在學習同步與異步之前,我們需要先研究幾個問題

在說到異步前,先來理一下幾個容易混淆的概念,並行、多線程、異步。

    並行,一般指並行計算,是說同一時刻有多條指令同時被執行,這些指令可能執行於同一CPU的多核上,或者多個CPU上,或者多個物理主機甚至多個網絡中。

    多線程,一般指同一進程中多個線程(包含其數據結構、上下文與代碼片段)協作運行。在多核計算機中多個線程將有機會同時運行於多個核上,如果線程中進行的是計算,則行成並行計算。

    異步,與同步相對應,是指呼叫另一操作后,不等待其結果,繼續執行之后的操作,若之后沒有其他操作,當前線程將進入睡眠狀態,而CPU時間將有機會切至其他線程。在異步操作完成后通過回調函數的方式獲取通知與結果。異步的實現方式有多種,如多線程與完成端口。多線程將異步操作放入另一線程中運行,通過輪詢或回調方法得到完成通知;完成端口,由操作系統接管異步操作的調度,通過硬件中斷,在完成時觸發回調方法,此方式不需要占用額外線程。

通過上面的兩張圖,可以把三個概念透析的非常好理解,異步在某種意義上講是“時空轉換”即時間換空間,空間換時間。下邊我們來學習下,在net 中的異步

 

二、同步和異步

1.同步執行

為了准備一個耗時的程序,本人准備了一本Txt修仙小說,我們用程序讀取一行行輸出,輸出完成以后,我們輸出一句話,"今天書就讀到這里吧!!累了,休息一會,休息一會!一休哥",為了更好的演示同步異步,本文采用winform程序,同時為了體驗winform 和控制台 帶來的視覺效果,我們選擇項目屬性,應用程序這選擇控制台。

在准備一個很費時的讀書方法,

  /// <summary>
        /// 讀書,一個很廢時間的任務
        /// </summary>
        public void ReadBook()
        {
            //我們可以通過 Thread.CurrentThread.ManagedThreadId 獲取當前線程的唯一標識符
            Console.WriteLine("********************** ReadBook Start【" + Thread.CurrentThread.ManagedThreadId + "】等待............... **********************************************");
            System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
            watch.Start();
            string Path= AppDomain.CurrentDomain.BaseDirectory + "zqjz.txt";
            List<string> list = new List<string>();
            System.IO.StreamReader sr = new System.IO.StreamReader(Path, Encoding.Default);
            string line = "";
            Console.ForegroundColor = ConsoleColor.Black;
            while ((line = sr.ReadLine()) != null&& list.Count<120)
            {
              char[] array=  line.ToArray();
                for (int i = 0; i < array.Length; i++)
                {
                  
                    Console.Write(array[i]);
                    if (i!=0)
                    {
                        //  Thread.Sleep(128);//人眼最快敏感視覺是128毫秒左右,我們這里測試先使用10毫秒
                        Thread.Sleep(10);
                    }
                }
                Console.WriteLine();
                Console.BackgroundColor = (ConsoleColor)new Random().Next(3, 15);
                list.Add(line);
            }
            sr.Close();
            sr.Dispose();
            watch.Stop();
            Console.WriteLine("今天讀書用了"+ watch.ElapsedMilliseconds+"豪秒");
            Console.WriteLine("********************** ReadBook End【" + Thread.CurrentThread.ManagedThreadId + " 】**********************************************");
        }

 這個方法比較簡單,就是讀取電子書,同時給方法加上了耗時記錄,和當前線程的唯一標識。現在我們在窗體上加上一個buttion 調用下我們的讀書。看看結果是怎么樣的,同時建議打開任務管理器,監控下CPU,等cpu 平穩以后,我們在點擊同步執行按鈕。“現在是我們自己在讀書”。

2.異步執行

關於異步在前邊的摘論里面介紹了大概,這里不過多演示,請繼續看!在早期,net 的異步都是在使用委托來做的,而委托使用的是線程池ThreadPool來實現的,曾取下一篇文章介紹線程,到時候在詳細介紹線程池,關於委托請觀看本人前邊的文章 "linq to Objet",我們在程序上在加上一個按鈕,里面老師讀書,我的心缺飛了,在想下課玩什么?怎么和同學玩。

 private void btnSync_Click(object sender, EventArgs e)
        {//同步
            Console.WriteLine("**********************btnSync_Click Start【" + Thread.CurrentThread.ManagedThreadId + "】**********************************************");
            ReadBook();
            MessageBox.Show("今天書就讀到這里吧!!累了,休息一會,休息一會!一休哥");
            Console.WriteLine("**********************btnSync_Click End 【" + Thread.CurrentThread.ManagedThreadId + "】**********************************************");
        }
        private void btnasync_Click(object sender, EventArgs e)
        {//異步
            Console.WriteLine("**********************btnSync_Click Start【" + Thread.CurrentThread.ManagedThreadId + "】**********************************************");
            Action action = new Action(() => ReadBook());
            action.BeginInvoke(null,null);//參數先不管,我們先給null,一會我們會繼續演示
            MessageBox.Show("今天想玩,怎么騙過老師呢!!書還在繼續讀,但是我已經在玩了!!!");
            Console.WriteLine("**********************btnSync_Click End 【" + Thread.CurrentThread.ManagedThreadId + "】**********************************************");
        }

 

 上面代碼分別為異步調用和同步調用,下圖為異步調用結果,我們會發現,異步調用窗體是可以移動的,並且CPU 會有很大的波峰,細心的人會發現,執行時間是一樣的,只是他們的線程唯一標識是不一樣的。

 

 

 通過上述演示,異步和同步的區別很簡單了吧!這里就不過多描述,自己總結。但是我們的要說下異步和多線程的區別?其實異步只是一種結果(目地),而多線程才是實現這種結果的一種方式,在NET 里面,異步和多線程沒有本質的區別,個人總結唯一的區別就是,應用場景不同。

 重點:多播委托不可以指定異步。不予顯示,自己去嘗試和找尋原理,實在找不到原理可以理解為這是任何高級語言的一個規定。有關多播委托請參考本人:一步一步帶你了解 Linq to Object

三、異步回掉和異步等待(阻塞)

 1.異步回掉:

 剛才我們一直在上課讀書,但是我的心里在想的是下課去哪里玩,如何玩?這個時候,我們需要在異步讀書的方法之后也就是下課以后再去玩。看下代碼是怎么寫的。

//異步
            Console.WriteLine("**********************btnSync_Click Start【" + Thread.CurrentThread.ManagedThreadId + "】**********************************************");
            #region  異步回調
            IAsyncResult iAsyncResult = null;
            AsyncCallback callback = t =>
            {
                Console.WriteLine(t);
                Console.WriteLine("下邊代碼是比較兩個對象是否一樣");
                Console.WriteLine($"string.ReferenceEquals(t, iAsyncResult)={string.ReferenceEquals(t, iAsyncResult)}");
                Console.WriteLine($"當前線程ID {Thread.CurrentThread.ManagedThreadId}");
                Console.WriteLine($"終於下課了!我們走吧,盡情的玩吧,你問我老師講的啥,我知道!!");
            };//AsyncCallback 本身就是一個委托,這個委托有一個參數,這個參數就是我們委托的BeginInvoke的返回值。我們使用這個委托去做異步回調
            #endregion
            Action action = () => ReadBook();//簡寫
            iAsyncResult= action.BeginInvoke(callback, null);//這里的第一個參數,我們就是異步回調
            MessageBox.Show("今天想玩,怎么騙過老師呢,下課玩點什么呢!!書還在繼續讀,但是我的心已經飛了!!!");
            
            Console.WriteLine("**********************btnSync_Click End 【" + Thread.CurrentThread.ManagedThreadId + "】**********************************************");

 

 

所謂的異步回調,就是在異步線程執行完在執行的代碼塊。

2.異步等待(阻塞):

 

主線程等待子線程有這么幾種方式:

1.主線程等待子線程的時候有返回,比如說我們常見的進度條功能,執行一點我就返回下。2.主線程等待子線程有時間限制,例如:中午放學我等你五分鍾,你要是不完事,我就先吃飯去了。3.主線程等待子線程無返回,比如死等,今天的代碼我學不會了,我就不睡覺了。下面我們分別看看這三種情況。我們管操作線程等待的過程叫做阻塞(se)進程.阻塞主進程以后等待子線程的執行,我們成為線程的阻塞,

剛才我們是使用回調,在異步執行完成,才執行了一個代碼塊,這個時候messagebax 已經輸出了,現在我們開看看課堂下的學生表現。“將下列代碼放到我們  MessageBox.Show("今天想玩,怎么騙過老師呢,下課玩點什么呢!!書還在繼續讀,但是我的心已經飛了!!!");”之后,我們來看看

 while (!iAsyncResult.IsCompleted)//邊等待邊操作,可以用於做進度條
            {
                Thread.Sleep(100);//建議控制100毫秒一次
                Console.WriteLine("老師還在教我們讀書.....請等待...............");
             
            }
            //當異步完成以后,我們在執行下邊的這句話
            Console.WriteLine("學生甲:沖啊..............打籃球全");
            Console.WriteLine("學生乙:王美美.......我愛你!咱們交往吧....*#*#*#**??!");
            Console.WriteLine("學生丙:呼呼呼呼呼呼呼呼。。。。。嚕。。。。。。。。。。今天的肉丸子真好吃,真希望這不是夢啊");
            Console.WriteLine("學生丁:大海啊,就像媽媽一樣,海浪啊!你為啥這么猛!總是在我人生巔峰......被打斷");
            Console.WriteLine("學生丙:別BiBi了,海浪是你后媽,滾一邊去淫詩去!別TMD打擾老子睡覺");

 

剛才執行的線程等待在阻塞的過程中是有損耗的,我們損耗 的是時間,所以回調會在子線程之前執行,那么我們想要無損耗怎么去寫,怎么去阻塞我們的主線程呢 “ bool RunBool = iAsyncResult.AsyncWaitHandle.WaitOne();”; 當子線程執行成功了,就會返回TRUE,當子線程執行過程中出現exection 以后,就返回false;

這種寫法主線程就無法返回了。但是我們可以新建立一個線程去監控子線程。這里就不寫那么復雜了。

第二種情況,我只等你兩秒鍾,有時間限制的阻塞

 

   #region 異步等待1 有損耗 帶返回
            //while (!iAsyncResult.IsCompleted)//邊等待邊操作,可以用於做進度條
            //{
            //    Thread.Sleep(100);//建議控制100毫秒一次
            //    Console.WriteLine("老師還在教我們讀書.....請等待...............");

            //}
            #endregion
            #region 異步等待2 無損耗 無返回
            //bool RunBool = iAsyncResult.AsyncWaitHandle.WaitOne();//返回結果是子線程執行成功或者失敗,不是實時返回的。
            //iAsyncResult.AsyncWaitHandle.WaitOne(-1);//寫法2 
            #endregion
            #region 有時間限制的異步等待
            iAsyncResult.AsyncWaitHandle.WaitOne(2000);//我最多等你2秒鍾,如果你提前完事,我們提前走
            #endregion 
            //當異步完成以后,我們在執行下邊的這句話
            Console.WriteLine("學生甲:沖啊..............打籃球全");
            Console.WriteLine("學生乙:王美美.......我愛你!咱們交往吧....*#*#*#**??!");
            Console.WriteLine("學生丙:呼呼呼呼呼呼呼呼。。。。。嚕。。。。。。。。。。今天的肉丸子真好吃,真希望這不是夢啊");
            Console.WriteLine("學生丁:大海啊,就像媽媽一樣,海浪啊!你為啥這么猛!總是在我人生巔峰......被打斷");
            Console.WriteLine("學生丙:別BiBi了,海浪是你后媽,滾一邊去淫詩去!別TMD打擾老子睡覺");
            Console.WriteLine("**********************btnSync_Click End 【" + Thread.CurrentThread.ManagedThreadId + "】**********************************************");

 

 

這種子線程執行兩秒以后,主線程在執行這個問題經常會在面試里面問。面試經常會問,主線程A 至少要執行10,秒,子線程B至少要執行30秒,如何讓主線程在子線程執行20秒開始執行。

下邊我們就舉例,代碼不會,我就要學習了學習不會就不睡覺,就死學到底了。

    #region 異步等待死等
            //死等就是,只要你不異常,就必須給我一個結果,比如學習,必須學會為止
            action.EndInvoke(iAsyncResult);//EndInvoke 的返回值取決與你的委托!你的委托有返回值,我就有返回值。
            #endregion 

 

 

注意圖上反應的問題。其實回調執行的是子線程。我們死等(阻塞 主線程等待子線程)的是子線程,而不是子線程的回調。這個時候是主線程和子線程一起執行的(線程的無序)。這就會照成CPU 更大的波峰,很容易宕機。由於演示這種結果不容易,需要執行很多遍,這里沒有截取到CPU 波峰。本人I7 CPU 基本都趕到頂了。

通過上圖可以看出,主線程和子線程的執行先后順序不一定誰先后,線程是無序的。

如果下了本文demo 的同學會發現,這個時候UI 是卡住的,主窗體UI阻塞,所以窗體是無法移動的。

。到這里異步我們就學習完了,下邊總結下

 

四、總結

 1.異步等待和異步回調的區別?面試會考的哦!!

答:異步等待是在子線程執行完成以后回到主線程,j解除主線程的阻塞繼續執行,而異步回調是子線程執行完成以后在去以子線程再去執行的任務代碼塊。

異步等待卡主線程,回調不卡主線程。

委托中回調不可以取得子線程執行的結果,等待可以通過線程狀態參數取得執行結果。

2.主線程A 需要執行1秒,而子線程B需要執行3秒。如果讓B執行2秒以后在執行?或者 接口A 調用5秒沒結果,我就調用接口B去取數據?在接口B取到數據以后,接口如果也取到數據,仍然使用結果B的,怎么去做。

答:使用 iAsyncResult.AsyncWaitHandle.WaitOne(2000);

 關於接口(WebApi ,Service)的情況,我們也是需要使用線程等待,但是這個時候我們就要加鎖或者加計時器 StopWatch 去做。關於鎖以后在談。但是加鎖會影響效率,計時器在多服務情況下還不准確,這是大多數面試者的回答。

我們把沒有演示的一點點知識在這里演示下。

 

我們一直沒有說這個參數有什么做用,這里簡單介紹下。當我線程啟動的時候,我可以啟動多條線程,但是我無法確定那個線程執行的過程,這個時候我們可以通過這個參數傳遞線程狀態。這里不過多解釋。有用到的私聊本人。

3.如果我想使用子線程的結果去做主線程的參數,如何去做。請說明你的理由。這里不過多解釋了,案列很清晰。

4.這里的阻塞是卡主線程的,我們如何不卡主線程??

下節多線程中找答案。

 

個人總結:

1.net 異步支持

Net framework可以讓你異步調用任何方法。為達這樣的目的,你可以定義一個與你要調用的方法的簽名相同的委托。公共語言運行時將自動為該委托定義與簽名相同的BeginInvok和EndInvoke方法。

異步委托調用BeginInvok和EndInvoke方法,但在.NET Compact Framework中並不支持。

 

.NET Framework 允許您異步調用任何方法。定義與您需要調用的方法具有相同簽名的委托;公共語言運行庫將自動為該委托定義具有適當簽名

的 BeginInvoke 和 EndInvoke 方法。

BeginInvoke 方法用於啟動異步調用。它與您需要異步執行的方法具有相同的參數,只不過還有兩個額外的參數(將在稍后描述)。

BeginInvoke 立即返回,不等待異步調用完成。
BeginInvoke 返回 IasyncResult,可用於監視調用進度。

EndInvoke 方法用於檢索異步調用結果。調用 BeginInvoke 后可隨時調用 EndInvoke 方法;如果異步調用未完成,EndInvoke 將一直阻塞到

異步調用完成。EndInvoke 的參數包括您需要異步執行的方法的 out 和 ref 參數(在 Visual Basic 中為 <Out> ByRef 和 ByRef)以及由

BeginInvoke 返回的 IAsyncResult。

四種使用 BeginInvoke 和 EndInvoke 進行異步調用的常用方法。調用了 BeginInvoke 后,可以:

1.進行某些操作,然后調用 EndInvoke 一直阻塞到調用完成。
2.使用 IAsyncResult.AsyncWaitHandle 獲取 WaitHandle,使用它的 WaitOne 方法將執行一直阻塞到發出 WaitHandle 信號,然后調用

EndInvoke。這里主要是主程序等待異步方法,等待異步方法的結果。
3.輪詢由 BeginInvoke 返回的 IAsyncResult,IAsyncResult.IsCompeted確定異步調用何時完成,然后調用 EndInvoke。此處理個人認為與
相同。
4.將用於回調方法的委托傳遞給 BeginInvoke。該方法在異步調用完成后在 ThreadPool 線程上執行,它可以調用 EndInvoke。這是在強制裝

換回調函數里面IAsyncResult.AsyncState(BeginInvoke方法的最后一個參數)成委托,然后用委托執行EndInvoke。
警告   始終在異步調用完成后調用 EndInvoke。

通過EndInvoke方法檢測異步調用的結果。如果異步調用尚未完成,EndInvoke將阻塞調用線程,直到它完成。EndInvoke參數包括out和ref參數,本文沒有講到,另外本文沒有演示EndInvoke 返回值 。

2.同步方法和異步方法的區別

同步方法調用在程序繼續執行之前需要等待同步方法執行完畢返回結果
異步方法則在被調用之后立即返回以便程序在被調用方法完成其任務的同時執行其它操作

 Demo 下載

 下篇 多線程Thread

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM