爲何不是摘要呢?其實這個是我我的的想法,其實不少人在談論異步與同步的時候都忽略了,同步異步不是軟件的原理,其自己是計算機的原理及概念,這裏就不過多的闡述計算機原理了。在學習同步與異步以前,咱們須要先研究幾個問題html
在說到異步前,先來理一下幾個容易混淆的概念,並行、多線程、異步。面試
並行,通常指並行計算,是說同一時刻有多條指令同時被執行,這些指令可能執行於同一CPU的多核上,或者多個CPU上,或者多個物理主機甚至多個網絡中。網絡
多線程,通常指同一進程中多個線程(包含其數據結構、上下文與代碼片斷)協做運行。在多核計算機中多個線程將有機會同時運行於多個核上,若是線程中進行的是計算,則行成並行計算。數據結構
異步,與同步相對應,是指呼叫另外一操做後,不等待其結果,繼續執行以後的操做,若以後沒有其餘操做,當前線程將進入睡眠狀態,而CPU時間將有機會切至其餘線程。在異步操做完成後經過回調函數的方式獲取通知與結果。異步的實現方式有多種,如多線程與完成端口。多線程將異步操做放入另外一線程中運行,經過輪詢或回調方法獲得完成通知;完成端口,由操做系統接管異步操做的調度,經過硬件中斷,在完成時觸發回調方法,此方式不須要佔用額外線程。多線程
經過上面的兩張圖,能夠把三個概念透析的很是好理解,異步在某種意義上講是「時空轉換」即時間換空間,空間換時間。下邊咱們來學習下,在net 中的異步dom
爲了準備一個耗時的程序,本人準備了一本Txt修仙小說,咱們用程序讀取一行行輸出,輸出完成之後,咱們輸出一句話,"今天書就讀到這裏吧!!累了,休息一會,休息一會!一休哥",爲了更好的演示同步異步,本文采用winform程序,同時爲了體驗winform 和控制檯 帶來的視覺效果,咱們選擇項目屬性,應用程序這選擇控制檯。異步
在準備一個很費時的讀書方法,async
/// <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 平穩之後,咱們在點擊同步執行按鈕。「如今是咱們本身在讀書」。函數
關於異步在前邊的摘論裏面介紹了大概,這裏不過多演示,請繼續看!在早期,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
剛纔咱們一直在上課讀書,可是個人內心在想的是下課去哪裏玩,如何玩?這個時候,咱們須要在異步讀書的方法以後也就是下課之後再去玩。看下代碼是怎麼寫的。
//異步 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 + "】**********************************************");
所謂的異步回調,就是在異步線程執行完在執行的代碼塊。
主線程等待子線程有這麼幾種方式:
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 返回值 。
同步方法調用在程序繼續執行以前須要等待同步方法執行完畢返回結果
異步方法則在被調用以後當即返回以便程序在被調用方法完成其任務的同時執行其它操做