寫在前面 在學非同步,有位園友推薦了《async in C#5.0》,沒找到中文版,恰巧也想提高下英文,用我拙劣的英文翻譯一些重要的部分,純屬娛樂,簡單分享,保持學習,謹記謙虛。 如果你覺得這件事兒沒意義翻譯的又差,盡情的踩吧。如果你覺得值得鼓勵,感謝留下你的贊,在今後每一次應該猛烈突破的時候,不選擇 ...
寫在前面
在學非同步,有位園友推薦了《async in C#5.0》,沒找到中文版,恰巧也想提高下英文,用我拙劣的英文翻譯一些重要的部分,純屬娛樂,簡單分享,保持學習,謹記謙虛。
如果你覺得這件事兒沒意義翻譯的又差,盡情的踩吧。如果你覺得值得鼓勵,感謝留下你的贊,在今後每一次應該猛烈突破的時候,不選擇知難而退。在每一次應該獨立思考的時候,不選擇隨波逐流,應該全力以赴的時候,不選擇儘力而為,願愛技術的園友們不辜負每一秒存在的意義。
轉載和爬蟲請註明原文鏈接http://www.cnblogs.com/tdws/p/5618321.html,博客園 蝸牛 2016年6月25日6:15pm。
目錄 桌面應用程式
打個比方:咖啡館
Web應用程式服務端代碼
另一個比方:餐館廚房
Silverlight, Windows Phone, and Windows 8
並行代碼
一個示例
非同步編程很重要很有用處,原因有很多,主要看你在構建什麼類型的應用程式。其中一部分的用處和益處在任何應用程式中都隨處可見,及時某些類型的應用程式你沒有接觸過。如果這適合你,請閱讀整篇文章,做為背景知識將會幫助你更好的理解整個上下文。
桌面應用程式桌面app有一個主要的性能要求。要求app讓用戶一直感覺到是可響應的。HCI研究表明,緩慢的應用程式不會得到用戶的關註,最好是要有一個進度條提示器。
當應用程式凍住或者說未響應狀態,用戶會變得沮喪。凍結的原因通常是因為一個耗時操作,或者因為一個緩慢的計算,或者因為IO,或者網路請求。
你用的C#的桌面UI框架都是單個UI線程的,包括:
·Winforms
·WPF
·Silverlight
UI線程是唯一的可以控制特性視窗的,也是唯一一個線程來檢查用戶輸入和為他們執行相應工作。如果這個線程很繁忙或者被阻塞數幾十毫秒,用戶就會註意到這個app是緩慢的。
非同步代碼,甚至手工編寫,意味著UI線程可以返回檢查消息隊列進行對用戶操作的主要工作,並對他們響應。它也可以執行進度動畫,併在最近版本的視窗,滑鼠懸停動畫,這都是重要的視覺線索給用戶,給人一個好印象的響應的應用程式。
打個比方:咖啡館所有常見的用戶界面框架只使用一個線程的原因是為了簡化同步。如果有很多線程的話,當一個線程正在鋪設佈局控制項過程中,另一個線程試圖讀取按鈕的寬度,這樣衝突了。為了避免這樣的事情發生,你需要大量的使用鎖,這將會大大降低程式的性能。
我想用一個比喻來直觀的幫你把握所涉及的問題。如果你覺得已經理解,那麼請跳過這一小節。
想象一下,有個小咖啡館為顧客早餐提供麵包,唯一的工作人員就是老闆,他非常註重和關心客戶服務,但是還沒有學會非同步的技術。UI線程模型和咖啡店的老闆很相似,就像在電腦中必須通過線程一樣,咖啡店員工在咖啡館中工作,在這樣的情況下,就像他們只有一個UI線程一樣。
第一個客戶要一片麵包,老闆將麵包放到烤箱中,然後他在烤麵包的過程中等待著。客戶問老闆在哪能找到黃油,但是老闆把她忽略了,就像他是阻塞型代碼一樣。五分鐘過去,麵包已經烤好並且拿給了客戶。到了這個時候,已經排起了長隊,客戶最討厭的就是等待和被忽略,老闆這樣做一點也不理想。
現在,我們來教老闆非同步。
首先要確保他的烤箱可以非同步操作,當我們編寫非同步代碼時,我們要確保耗時操作在執行結束時能夠告訴我們,同樣的,麵包烤箱也需要一個定時器,並且要響亮以便能夠被他註意到。
另外就是他能夠在烤箱開始的時候忽略它,他應該回去繼續為顧客服務,同樣的,我們的非同步代碼也要在執行耗時操做時返回,這樣UI線程可以繼續相應用戶操作。
有兩點原因:
·可以更好的響應用戶請求—客戶可以詢問黃油在哪並且不被老闆忽略。
·用戶可以同時開始另一個操作—下一個客戶也可以開始點餐並且讓老闆烹飪。
咖啡館老闆現在可以同時為多個客戶服務,唯一的限制就是烤箱的數量和拿取麵包的時間。但這樣帶來了一系列問題:老闆發現自己並不能記住哪片麵包是哪個客戶所點,UI線程完全不能記住當他返回時,他在等待的是哪個操作。
所以我們必須再開始的時候附加一個回調,去提醒我們當他結束時該做什麼。至於咖啡館老闆,很簡單的就是在烤箱上貼上客戶姓名的標簽。但我們可能需要更複雜的東西,一般來說我們希望能夠對耗時工作結束後需要做什麼事情,提供完整說明,一旦完成了工作。
有了這些,咖啡館老闆現在完全是非同步的了,並且生意紅火。用戶體驗感變得更好。這就是等待很少,更具效應能力。希望這個比方能夠幫助你的直覺來理解為什麼非同步在UI application上如此重要。
Web應用程式服務端代碼ASP.NET的Web伺服器沒有像UI代碼一個線程的的硬限制。這就是說,在web中使用多線程編程依然有很多好處。耗時操作,尤其是遠程資料庫Query,在web app中非常常見。
取決於你的IIS版本,將會有用於處理web請求的線程總數和併發請求數的限制。如果你的請求花了大量的時間在等待資料庫Query上,那麼增加併發請求數去增加伺服器吞吐量是一個好的方法。
當一個線程被阻塞,一直在等待,它不占用UPU時間。然而,你不要誤以為這意味著它不占用伺服器資源。事實上,線程占用兩項重要的開銷,即使他們在被阻塞:
·記憶體
每個托管線程儲備在Windows上的虛擬記憶體的位元組。如果你有幾十個線程是完全沒有問題的,但是當你有上百個線程時很容易就會失控。如果記憶體使用了磁碟上的虛擬記憶體空間,那麼你的線程會變得特別慢。
·調度器的開銷
操作系統的調度器負責選擇在什麼時間,在哪個CPU上,執行哪個線程。即使當線程被阻塞時,調度器也必須考慮到他們,並且判斷它們是否變得非阻塞(阻塞結束)。這樣減緩了線程上下文切換,甚至可以拖慢整個系統。
在他們之間,這些開銷負載到到你的伺服器上,增加延遲並且降低吞吐量。
請記住:非同步編程的主要特征是當線程開始執行一段耗時操作,這個線程會被釋放去做其他一些事情。在ASP.NET代碼下,線程來自於線程池,所以在執行耗時操作期間,線程會被返回到線程池,他可以處理其他請求,以很少的線程就可以來處理同阻塞代碼情況相同的請求數量。//譯者註解,如果你不理解這句話,可以參照我的另一篇文章中的解惑二:文章鏈接
另一個比方:餐館廚房web伺服器和餐館模型很接近,很多客人點餐,廚房會儘可能的快速滿足他們的要求。
我們的廚房有很多廚師,每個廚師代表一個線程。他們按照用戶的訂單來烹飪,但是在整個準備過程中,每道菜只需要烹飪一會兒,並且廚師可能等待著沒什麼事情做。這反映了web請求處理的方式,通常在執行資料庫Query請求資料庫數據的這段時間,web伺服器並沒有參與。
在類似於阻塞型的廚房,廚師將會在烹飪工具前等待菜餚的烹飪。精確模擬一個線程,廚師有一個奇怪的合同,廚師在等待烹飪結束的過程中不被支付薪水,因為一個線程不占用CPU時間當他們被阻塞時,也需要他們在這期間讀報紙。
但即使我們沒有向他們支付,我們依然可以使用新廚師為另一道菜餚,那些等待烹飪結束的廚師依然空閑在廚房。但是,我們不能讓幾十個廚師在廚房工作,這樣連轉身都困難,最終導致每個人的工作效率都很低。
當然我們用非同步工作方式會更好,每當菜餚正在烤箱中烹飪,廚師記下當前在烹飪的是什麼菜餚,在什麼階段或者階段,然後找到一項新的任務(菜餚)去做。當菜餚烹飪結束,任意一個廚師可以將菜餚拿出來繼續處理。//譯者註釋:記住,每個廚師代表一個線程,試著當作線程來理解,你會明白原理。
Web伺服器正是這種強大的系統。是需要極少部分線程就可以承受以前的併發量,或者能做到以前在開銷上不可行的事情。事實上,一些web框架,特別是nodejs,拒絕多線程並行的方式,選擇單個線程來非同步處理所有請求。他們可以用單線程來處理比多線程並行更多的請求,但是阻塞,系統可以處理的總數。同樣的,一個組織能力強的廚師在一個空廚房中可以烹飪比,上百個廚師在廚房還多的菜餚,因為出事多了他們花了幾乎所有時間在絆倒彼此和閱讀報紙上。
Silverlight, Windows Phone, and Windows 8暫時不翻譯此部分
並行代碼電腦是多處理核心的,每個核心之間相互獨立。程式需要充分利用多核心的優勢,但由這些程式使用的任何記憶體不能被並行代碼立即同時寫入,否則記憶體容易被損害。
也許在編程中使用比較純凈的統一的風格是更好的,即不在記憶體中操作狀態,而是操作不變值。這將會讓我們享受並行系統的好處,但這也不適用於某些程式。就像User Interfaces需要狀態,資料庫就是狀態。//譯者註釋:原文的狀態是state,這個詞也許翻譯為狀態不恰當。
標準的解決方案是使用互斥鎖以防兵行代碼同時訪問相同記憶體。但這又帶來一系列問題,你的代碼通常會帶一把鎖,然後做出一個方法調用或註冊一個事件又帶另一個鎖。通常,同時保持兩個鎖不是必要的,但代碼是沒有人類這樣思想的。
下麵是一個對於鎖的假設結構,意思是說,總體來講,更多的線程結束,等待鎖,直到他們可以做一些有用的工作。在一些情況下,兩個線程同時等待另一個線程保持的鎖,引發了死鎖。這些錯誤是很難預測,很難重現,而且往往很難修複。
最有前景的解決方案之一是Actor模型計算。這是一個在每塊可寫的記憶體只能存在於一個Actor內的設計。唯一的方式來使用這塊記憶體是向Actor發送消息,從而一次處理一個,並且可能會得到另一條回覆消息。這就是非同步編程。詢問Actor的操作是一種典型的非同步操作,因為我們可以繼續做其他事情直到回覆消息抵達。這意味著你可以使用非同步來做,詳細將會見第10章.
一個示例我們將會看到一個desktop UI app,它急需轉換為非同步風格。源代碼地址https://bitbucket.org/alexdavies74/faviconbrowser .我建議你如果可以的話跟隨著來進行,在VS中打開。
運行程式,你會看到一個視窗有一個按鈕。如果你按這個按鈕,它會顯示一些流行的網站的圖標。它通過下載大多數網站包含的一個文件名為favicon.ico(圖2-1)。
************************配圖 downloads the favicon and adds it to a WPF WrapPanel in the window.讓我們看一看代碼。重要的方法是下載的圖標,並將其添加到視窗中的WPF wrappanel。
private void AddAFavicon(string domain) { WebClient webClient = new WebClient(); byte[] bytes = webClient.DownloadData("http://" + domain + "/favicon.ico"); Image imageControl = MakeImageControl(bytes); m_WrapPanel.Children.Add(imageControl); }
你將會註意到代碼但全是同步的,線程在下載的過程中是阻塞的。你可能還會註意到點擊按鈕的時候,視窗有幾秒變成了未響應的狀態。和你知道的一樣,這是因為在下載小圖標icons時UI線程阻塞,並且不能返回處理用戶的操作。
我們將使用這個例子在接下來的章節,並將其轉化為非同步編程的程式。
寫在最後早上九點多開始,除了吃飯,健身,基本一直在翻譯和學習第二章。我覺得對於ASP.NET非同步編程,廚房這個這個比方簡直太棒太恰當了!如果你對你有些許益處,不要吝嗇你的贊,給個鼓勵。不准確的地方,也請前輩們不吝賜教,我將虛心改正。