在學異步,有位園友推薦了《async in C#5.0》,沒找到中文版,恰巧也想提升下英文,用我拙劣的英文翻譯一些重要的部分,純屬娛樂,簡單分享,保持學習,謹記謙虛。html
若是你以爲這件事兒沒意義翻譯的又差,盡情的踩吧。若是你以爲值得鼓勵,感謝留下你的贊,願愛技術的園友們在從此每一次應該猛烈突破的時候,不選擇知難而退。在每一次應該獨立思考的時候,不選擇隨波逐流,應該盡心盡力的時候,不選擇盡力而爲,不辜負每一秒存在的意義。web
轉載和爬蟲請註明原文連接http://www.cnblogs.com/tdws/p/5628538.html,博客園 蝸牛 2016年6月27日。編程
在本章,咱們將會討論一些關於不使用C#5.0關鍵字async的異步編程。這種方式雖然已是過去的技術,也許你不會再使用,但這對於你理解異步編程表象背後發生了什麼事情是很重要的。也由於這一點,我將會很快的講述示例,僅僅着重揭示出對你理解有幫助的地方。
正如我以前提到的,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屬性時,將會從新拋出異常,因此在代碼中咱們能夠相應的處理異常。相反,他們可能在某個錯誤地方中止或者根本不去處理。
任務並行實在.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方法」