異步編程系列第03章 本身寫異步代碼

寫在前面

  在學異步,有位園友推薦了《async in C#5.0》,沒找到中文版,恰巧也想提升下英文,用我拙劣的英文翻譯一些重要的部分,純屬娛樂,簡單分享,保持學習,謹記謙虛。html

  若是你以爲這件事兒沒意義翻譯的又差,盡情的踩吧。若是你以爲值得鼓勵,感謝留下你的贊,願愛技術的園友們在從此每一次應該猛烈突破的時候,不選擇知難而退。在每一次應該獨立思考的時候,不選擇隨波逐流,應該盡心盡力的時候,不選擇盡力而爲,不辜負每一秒存在的意義。web

   轉載和爬蟲請註明原文連接http://www.cnblogs.com/tdws/p/5628538.html,博客園 蝸牛 2016年6月27日。編程

目錄

第01章 異步編程介紹app

第02章 爲何使用異步編程dom

第03章 手動編寫異步代碼異步

    .NET中的一些異步模式
    最簡單的異步模式
    關於Task的介紹
    手動編寫異步代碼的問題
    使用手寫異步代碼轉換示例(第二章最後一個示例)
async

第04章 編寫Async方法異步編程

第05章 Await究竟作了什麼工具

第06章 以Task爲基礎的異步模式性能

第07章 異步代碼的一些工具

第08章 哪一個線程在運行你的代碼

第09章 異步編程中的異常

第10章 並行使用異步編程

第11章 單元測試你的異步代碼

第12章 ASP.NET應用中的異步編程

第13章 WinRT應用中的異步編程

第14章 編譯器在底層爲你的異步作了什麼

第15章 異步代碼的性能

手動編寫異步代碼

  在本章,咱們將會討論一些關於不使用C#5.0關鍵字async的異步編程。這種方式雖然已是過去的技術,也許你不會再使用,但這對於你理解異步編程表象背後發生了什麼事情是很重要的。也由於這一點,我將會很快的講述示例,僅僅着重揭示出對你理解有幫助的地方。

 
.NET中的一些異步模式

  正如我以前提到的,Silverlight只提供了像web訪問的異步版本API。這裏有一個例子,你能夠下載一個網頁,並顯示它:

private void DumpWebPage(Uri uri)
{
WebClient webClient = new WebClient();
webClient.DownloadStringCompleted += OnDownloadStringCompleted;
webClient.DownloadStringAsync(uri);
}
private void OnDownloadStringCompleted(object sender,
DownloadStringCompletedEventArgs eventArgs)
{
m_TextBlock.Text = eventArgs.Result;
}

  這種API是基於事件的異步模式(EAP)。這個想法是想替代單線程方法去下載網頁,即阻塞型代碼會一直等到下載結束再調用一個方法或觸發一個事件。這個方法看起來和同步代碼同樣,除了無返回類型。這個事件也有一個特別的eventArgs類型,它包含值檢索。

  咱們在調用這個方法前註冊了事件。該方法當即返回,固然這是由於它是異步代碼。而後在未來的某個時刻觸發。這種模式顯然很複雜,不只僅是由於你要將它分紅像例子同樣的兩個方法。最重要的是,你註冊了一個時間增長了複雜性。若是說我還要用相同的WebClient實例處理其餘需求,那麼你也許不但願這個時間依然被附加着而且再次執行一遍。

  在.NET功能中另外一個異步模式設計IAsyncResult接口。其中一個例子就是DNS查找主機名的IP地址,BeginGetHoseAddress。這種設計要求兩個方法,一個是開始執行的BeginMethodName,另外一個是執行結束EndMethodName,即你的回調方法。

private void LookupHostName()
{
object unrelatedObject = "hello";
Dns.BeginGetHostAddresses("oreilly.com", OnHostNameResolved, unrelatedObject);
}
private void OnHostNameResolved(IAsyncResult ar)
{
object unrelatedObject = ar.AsyncState;
IPAddress[] addresses = Dns.EndGetHostAddresses(ar);
// Do something with addresses
...
}

  至少這種方式不會遭受殘留註冊事件的影響,然而這也額外的對API增長了複雜性。有兩個方法而不是一個,我以爲很不天然。

  這兩種異步模式都須要你分爲兩個方法來書寫。IAsyncResult模式要你從第一個方法中向第二個方法傳遞某些參數,就像我傳遞了string類型的"hello"。可是這種方式很複雜,即便你不須要這個參數,仍是不得不傳遞它,而且迫使你轉換爲object類型。

 
最簡單的異步模式

  能夠說下面這段代碼擁有異步行爲,即便不使用async關鍵字,也不用向方法傳遞委託:

void GetHostAddress(string hostName, Action<IPAddress> callback)

  我發現這種方式比其餘方式更加易用。

private void LookupHostName()
{
GetHostAddress("oreilly.com", OnHostNameResolved);
}
private void OnHostNameResolved(IPAddress address)
{
// Do something with address
...
}

  不一樣於兩個方法的模式,像我之前提到的,使用異步方法或者用lambda表達式作回調。它擁有重要的好處就是能夠在第一個方法中訪問變量。

private void LookupHostName()
{
int aUsefulVariable = 3;
GetHostAddress("oreilly.com", address =>
{
// Do something with address and aUsefulVariable
...
});
}

  這個Lambda有一點難以閱讀,而且一般若是你使用多重的異步編程,你將須要不少Lambda表達式相互嵌套,你的代碼將會很快變得犬牙交錯和難以處理。

  這種簡單方法的缺點在於他們再也不對調用者拋出異常。在以前.NET異步編程中,調用EndMethodName或者獲得Result屬性時,將會從新拋出異常,因此在代碼中咱們能夠相應的處理異常。相反,他們可能在某個錯誤地方中止或者根本不去處理。

 
關於Task的介紹

  任務並行實在.NET Framework4.0版本中推出的。其最重要的地方是Task類,即表明一個正在執行的操做。 泛型版本的Task<T>, 當操做完成時返回類型爲T的值。

   在C#5.0 async功能上咱們大量的使用了Task,咱們將會稍後討論。然而即便沒有async,你依然可使用Task,尤爲是使用Task<T>來異步編程。這樣作就行,你開始一個返回Task<T>的操做,而後使用ContinueWith方法註冊你的回掉方法。

private void LookupHostName()
{
Task<IPAddress[]> ipAddressesPromise = Dns.GetHostAddressesAsync("oreilly.com");
ipAddressesPromise.ContinueWith(_ =>
{
IPAddress[] ipAddresses = ipAddressesPromise.Result;
// Do something with address
...
});
}

  Task的優勢就像這個DNS只須要一個方法,使API更加整潔。全部調用異步行爲相關的邏輯均可在Task類當中,因此它不須要在每個方法裏都進行復制。這個邏輯能夠作不少重要的事兒,好比處理異常和同步上下文(SynchronizationContexts)。這些,咱們將會在第八章討論,對於在一個特定線程上執行callback頗有用處(好比UI線程)。

  最重要的是,Task給咱們提供一種使用異步的相對抽象的操做方式。咱們能夠利用這種組合型去編寫咱們的工具,即在不少須要使用Task的狀況下提供給一些有用的行爲。咱們將會看到不少相關的工具組件(utilities)在第七章當中。

 
手動編寫異步代碼的問題

   正如咱們看到的,咱們有不少方式來實現異步編程。有一些方式比其餘方式整潔易懂易用,可是也但願你已經看出他們共有的缺陷。你打算寫的程序不得不分爲兩個方法:實際的方法和回調方法。還有使用異步方法或嵌套屢次lambda表達式做爲回調,使你的代碼一環套一環難以理解。

  實際上這裏還有另外一個問題。咱們已經說過調用一次異步方法的狀況,可是當你須要多個異步時會發生什麼呢?更糟糕的是,若是弄須要在循環中調用異步又會發生什麼呢?你爲一個方式是使用遞歸方法,這又比普通的循環難以閱讀多了。

private void LookupHostNames(string[] hostNames)
{
LookUpHostNamesHelper(hostNames, 0);
}
private static void LookUpHostNamesHelper(string[] hostNames, int i) { Task<IPAddress[]> ipAddressesPromise = Dns.GetHostAddressesAsync(hostNames[i]); ipAddressesPromise.ContinueWith(_ => { IPAddress[] ipAddresses = ipAddressesPromise.Result; // Do something with address ... if (i + 1 < hostNames.Length) { LookUpHostNamesHelper(hostNames, i + 1); } }); }

  哇!

  在這些異步編程方式中,引起的另外一個問題就是須要消耗大量代碼。若是你寫一些異步代碼,指望在其餘地方使用,你不得不提供API,若是API混亂或者忘記當時的初衷不能理解的話,將會事半功倍。異步代碼是會「傳染」的,所以不只你須要異步API,還影響調用者和調用者的調用者,知道整個程序亂成一團。

 
使用手寫異步代碼轉換示例(第二章最後一個示例)

  再來談談第二章最後一個示例,咱們討論了一個會因從網站下載icons,形成UI線程阻塞,並致使出現應用程序未響應的WPF UI app。如今咱們將會看到,將它轉化成手寫的異步代碼。

  第首先要作的就是找到一個異步API的版本,我用(WebClient。下載文件)。正如咱們已經看到的,WebClient方法使用基於事件的異步方式(EAP),因此咱們能夠在開始下載以前註冊一個事件做爲回調方法。

private void AddAFavicon(string domain)
{
WebClient webClient = new WebClient();
webClient.DownloadDataCompleted += OnWebClientOnDownloadDataCompleted;
webClient.DownloadDataAsync(new Uri("http://" + domain + "/favicon.ico"));
}
private void OnWebClientOnDownloadDataCompleted(object sender,
DownloadDataCompletedEventArgs args)
{
Image imageControl = MakeImageControl(args.Result);
m_WrapPanel.Children.Add(imageControl);
}

  固然,咱們的真正屬於一塊兒的邏輯要被分紅兩個方法。我不喜歡使用Lambda來代替剛纔的EAP,由於lambda會出如今真正開始下載前,我以爲這是不可讀的。

  這個版本的示例也能夠在線(https://bitbucket.org/alexdavies74/faviconbrowser)找到,(//譯者註釋:不運行此程序也不要緊,主要是體會下思路就好)在manual分支。若是你運行它,不進界面可相應,圖標也會逐一出現。正所以,咱們也引入了一個bug,如今因爲全部下載操做同時開始,icons的排序由其下載前後決定,而不是由個人前後請求來決定。若是你想檢驗本身是否理解手動編寫異步代碼,我建議你嘗試着解決此bug。在orderedManual分支下(上面列出的站點下)提供了一個解決方案。其餘更有效的解決方案也是有可能的。

寫在後面
27號入職,花了三天的業餘時間,坎坎坷坷的翻譯了第三章。若是你對你有些許益處,不要吝嗇你的贊,給個鼓勵。不許確和須要補充的地方,也請前輩們不吝賜教,我將虛心改正。下一章將會介紹 「 編寫Async方法
相關文章
相關標籤/搜索