本文是一篇介紹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 下載
88.睡覺