亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

異步編程中的最佳做法

2019-11-17 02:17:57
字體:
來源:轉載
供稿:網友

異步編程中的最佳做法

原文鏈接

近日來,涌現了許多關于 Microsoft .NET Framework 4.5 中新增了對 async 和 await 支持的信息。本文旨在作為學習異步編程的“第二步”;我假設您已閱讀過有關這一方面的至少一篇介紹性文章。本文不提供任何新內容,Stack Overflow、MSDN 論壇和 async/await FAQ 這類在線資源提供了同樣的建議。本文只重點介紹一些淹沒在文檔海洋中的最佳做法。

本文中的最佳做法更大程度上是“指導原則”,而不是實際規則。其中每個指導原則都有一些例外情況。我將解釋每個指導原則背后的原因,以便可以清楚地了解何時適用以及何時不適用。圖 1中總結了這些指導原則;我將在以下各節中逐一討論。

圖 1 異步編程指導原則總結

“名稱”說明異常
避免 Async Void最好使用 async Task 方法而不是 async void 方法事件處理程序
始終使用 Async不要混合阻塞式代碼和異步代碼控制臺 main 方法
配置上下文盡可能使用 ConfigureAwait(false)需要上下文的方法

避免 Async Void

Async 方法有三種可能的返回類型: Task、Task<T> 和 void,但是 async 方法的固有返回類型只有 Task 和 Task<T>。當從同步轉換為異步代碼時,任何返回類型 T 的方法都會成為返回 Task<T> 的 async 方法,任何返回 void 的方法都會成為返回 Task 的 async 方法。下面的代碼段演示了一個返回 void 的同步方法及其等效的異步方法:

void MyMethod(){ // Do synchronous work.Thread.Sleep(1000);}async Task MyMethodAsync(){ // Do asynchronous work.await Task.Delay(1000);}

返回 void 的 async 方法具有特定用途: 用于支持異步事件處理程序。事件處理程序可以返回某些實際類型,但無法以相關語言正常工作;調用返回類型的事件處理程序非常困難,事件處理程序實際返回某些內容這一概念也沒有太大意義。事件處理程序本質上返回 void,因此 async 方法返回 void,以便可以使用異步事件處理程序。但是,async void 方法的一些語義與 async Task 或 async Task<T> 方法的語義略有不同。

Async void 方法具有不同的錯誤處理語義。當 async Task 或 async Task<T> 方法引發異常時,會捕獲該異常并將其置于 Task 對象上。對于 async void 方法,沒有 Task 對象,因此 async void 方法引發的任何異常都會直接在 SynchronizationContext(在 async void 方法啟動時處于活動狀態)上引發。圖 2演示本質上無法捕獲從 async void 方法引發的異常。

圖 2 無法使用 Catch 捕獲來自 Async Void 方法的異常

PRivate async void ThrowExceptionAsync(){ throw new InvalidOperationException();}public void AsyncVoidExceptions_CannotBeCaughtByCatch(){ try { ThrowExceptionAsync(); } catch (Exception) { // The exception is never caught here!throw; }}

可以通過對 GUI/asp.net 應用程序使用 AppDomain.UnhandledException 或類似的全部捕獲事件觀察到這些異常,但是使用這些事件進行常規異常處理會導致無法維護。

Async void 方法具有不同的組合語義。返回 Task 或 Task<T> 的 async 方法可以使用 await、Task.WhenAny、Task.WhenAll 等方便地組合而成。返回 void 的 async 方法未提供一種簡單方式,用于向調用代碼通知它們已完成。啟動幾個 async void 方法不難,但是確定它們何時結束卻不易。Async void 方法會在啟動和結束時通知 SynchronizationContext,但是對于常規應用程序代碼而言,自定義 SynchronizationContext 是一種復雜的解決方案。

Async void 方法難以測試。由于錯誤處理和組合方面的差異,因此調用 async void 方法的單元測試不易編寫。MSTest 異步測試支持僅適用于返回 Task 或 Task<T> 的 async 方法。可以安裝 SynchronizationContext 來檢測所有 async void 方法都已完成的時間并收集所有異常,不過只需使 async void 方法改為返回 Task,這會簡單得多。

顯然,async void 方法與 async Task 方法相比具有幾個缺點,但是這些方法在一種特定情況下十分有用: 異步事件處理程序。語義方面的差異對于異步事件處理程序十分有意義。它們會直接在 SynchronizationContext 上引發異常,這類似于同步事件處理程序的行為方式。同步事件處理程序通常是私有的,因此無法組合或直接測試。我喜歡采用的一個方法是盡量減少異步事件處理程序中的代碼(例如,讓它等待包含實際邏輯的 async Task 方法)。下面的代碼演示了這一方法,該方法通過將 async void 方法用于事件處理程序而不犧牲可測試性:

private async void button1_Click(object sender, EventArgs e){ await Button1ClickAsync();}public async Task Button1ClickAsync(){ // Do asynchronous work.await Task.Delay(1000);}

如果調用方不希望 async void 方法是異步的,則這些方法可能會造成嚴重影響。當返回類型是 Task 時,調用方知道它在處理將來的操作;當返回類型是 void 時,調用方可能假設方法在返回時完成。此問題可能會以許多意外方式出現。在接口(或基類)上提供返回 void 的方法的 async 實現(或重寫)通常是錯誤的。某些事件也假設其處理程序在返回時完成。一個不易察覺的陷阱是將 async lambda 傳遞到采用 Action 參數的方法;在這種情況下,async lambda 返回 void 并繼承 async void 方法的所有問題。一般而言,僅當 async lambda 轉換為返回 Task 的委托類型(例如,Func<Task>)時,才應使用 async lambda。

總結這第一個指導原則便是,應首選 async Task 而不是 async void。Async Task 方法更便于實現錯誤處理、可組合性和可測試性。此指導原則的例外情況是異步事件處理程序,這類處理程序必須返回 void。此例外情況包括邏輯上是事件處理程序的方法,即使它們字面上不是事件處理程序(例如 ICommand.Execute implementations)。

始終使用 Async

異步代碼讓我想起了一個故事,有個人提出世界是懸浮在太空中的,但是一個老婦人立即提出質疑,她聲稱世界位于一個巨大烏龜的背上。當這個人問烏龜站在哪里時,老夫人回答:“很聰明,年輕人,下面是一連串的烏龜!”在將同步代碼轉換為異步代碼時,您會發現,如果異步代碼調用其他異步代碼并且被其他異步代碼所調用,則效果最好 — 一路向下(或者也可以說“向上”)。其他人已注意到異步編程的傳播行為,并將其稱為“傳染”或將其與僵尸病毒進行比較。無論是烏龜還是僵尸,無可置疑的是,異步代碼趨向于推動周圍的代碼也成為異步代碼。此行為是所有類型的異步編程中所固有的,而不僅僅是新 async/await 關鍵字。

“始終異步”表示,在未慎重考慮后果的情況下,不應混合使用同步和異步代碼。具體而言,通過調用 Task.Wait 或 Task.Result 在異步代碼上進行阻塞通常很糟糕。對于在異步編程方面“淺嘗輒止”的程序員,這是個特別常見的問題,他們僅僅轉換一小部分應用程序,并采用同步 API 包裝它,以便代碼更改與應用程序的其余部分隔離。不幸的是,他們會遇到與死鎖有關的問題。在 MSDN 論壇、Stack Overflow 和電子郵件中回答了許多與異步相關的問題之后,我可以說,迄今為止,這是異步初學者在了解基礎知識之后最常提問的問題: “為何我的部分異步代碼死鎖?”

圖 3演示一個簡單示例,其中一個方法發生阻塞,等待 async 方法的結果。此代碼僅在控制臺應用程序中工作良好,但是在從 GUI 或 ASP.NET 上下文調用時會死鎖。此行為可能會令人困惑,尤其是通過調試程序單步執行時,這意味著沒完沒了的等待。在調用 Task.Wait 時,導致死鎖的實際原因在調用堆棧中上移。

圖 3 在異步代碼上阻塞時的常見死鎖問題

public static class DeadlockDemo{ private static async Task DelayAsync() { await Task.Delay(1000); } // This method causes a deadlock when called in a GUI or ASP.NET context.public static void Test() { // Start the delay.var delayTask = DelayAsync(); // Wait for the delay to complete.delayTask.Wait(); }}

這種死鎖的根本原因是 await 處理上下文的方式。默認情況下,當等待未完成的 Task 時,會捕獲當前“上下文”,在 Task 完成時使用該上下文恢復方法的執行。此“上下文”是當前 SynchronizationContext(除非它是 null,這種情況下則為當前 TaskScheduler)。GUI 和 ASP.NET 應用程序具有 SynchronizationContext,它每次僅允許一個代碼區塊運行。當 await 完成時,它會嘗試在捕獲的上下文中執行 async 方法的剩余部分。但是該上下文已含有一個線程,該線程在(同步)等待 async 方法完成。它們相互等待對方,從而導致死鎖。

請注意,控制臺應用程序不會形成這種死鎖。它們具有線程池 SynchronizationContext 而不是每次執行一個區塊的 SynchronizationContext,因此當 await 完成時,它會在線程池線程上安排 async 方法的剩余部分。該方法能夠完成,并完成其返回任務,因此不存在死鎖。當程序員編寫測試控制臺程序,觀察到部分異步代碼按預期方式工作,然后將相同代碼移動到 GUI 或 ASP.NET 應用程序中會發生死鎖,此行為差異可能會令人困惑。

此問題的最佳解決方案是允許異步代碼通過基本代碼自然擴展。如果采用此解決方案,則會看到異步代碼擴展到其入口點(通常是事件處理程序或控制器操作)??刂婆_應用程序不能完全采用此解決方案,因為 Main 方法不能是 async。如果 Main 方法是 async,則可能會在完成之前返回,從而導致程序結束。圖 4演示了指導原則的這一例外情況: 控制臺應用程序的 Main 方法是代碼可以在異步方法上阻塞為數不多的幾種情況之一。

圖 4 Main 方法可以調用 Task.Wait 或 Task.Result

class Program{ static void Main() { MainAsync().Wait(); } static async Task MainAsync() { try { // Asynchronous implementation.await Task.Delay(1000); } catch (Exception ex) { // Handle exceptions.} }}

允許異步代碼通過基本代碼擴展是最佳解決方案,但是這意味著需進行許多初始工作,該應用程序才能體現出異步代碼的實際好處??赏ㄟ^幾種方法逐漸將大量基本代碼轉換為異步代碼,但是這超出了本文的范圍。在某些情況下,使用 Task.Wait 或 Task.Result 可能有助于進行部分轉換,但是需要了解死鎖問題以及錯誤處理問題。我現在說明錯誤處理問題,并在本文后面演示如何避免死鎖問題。

每個 Task 都會存儲一個異常列表。等待 Task 時,會重新引發第一個異常,因此可以捕獲特定異常類型(如 InvalidOperationException)。但是,在 Task 上使用 Task.Wait 或 Task.Result 同步阻塞時,所有異常都會用 AggregateException 包裝后引發。請再次參閱圖 4。MainAsync 中的 try/catch 會捕獲特定異常類型,但是如果將 try/catch 置于 Main 中,則它會始終捕獲 AggregateException。當沒有 AggregateException 時,錯誤處理要容易處理得多,因此我將“全局”try/catch 置于 MainAsync 中。

至此,我演示了兩個與異步代碼上阻塞有關的問題: 可能的死鎖和更復雜的錯誤處理。對于在 async 方法中使用阻塞代碼,也有一個問題。請考慮此簡單示例:

public static class NotFullyAsynchronousDemo{ // This method synchronously blocks a thread.public static async Task TestNotFullyAsync() { await Task.Yield(); Thread.Sleep(5000); }}

此方法不是完全異步的。它會立即放棄,返回未完成的任務,但是當它恢復執行時,會同步阻塞線程正在運行的任何內容。如果此方法是從 GUI 上下文調用,則它會阻塞 GUI 線程;如果是從 ASP.NET 請求上下文調用,則會阻塞當前 ASP.NET 請求線程。如果異步代碼不同步阻塞,則其工作效果最佳。圖 5是將同步操作替換為異步替換的速查表。

圖 5 執行操作的“異步方式”

執行以下操作&hellip;替換以下方式…使用以下方式
檢索后臺任務的結果Task.Wait 或 Task.Resultawait
等待任何任務完成Task.WaitAnyawait Task.WhenAny
檢索多個任務的結果Task.WaitAllawait Task.WhenAll
等待一段時間Thread.Sleepawait Task.Delay

總結這第二個指導原則便是,應避免混合使用異步代碼和阻塞代碼?;旌袭惒酱a和阻塞代碼可能會導致死鎖、更復雜的錯誤處理及上下文線程的意外阻塞。此指導原則的例外情況是控制臺應用程序的 Main 方法,或是(如果是高級用戶)管理部分異步的基本代碼。

配置上下文

在本文前面,我簡要說明了當等待未完成 Task 時默認情況下如何捕獲“上下文”,以及此捕獲的上下文用于恢復 async 方法的執行。圖 3中的示例演示在上下文上的恢復執行如何與同步阻塞發生沖突從而導致死鎖。此

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产精品无av码在线观看| 5566成人精品视频免费| 日韩免费黄色av| 亚洲黄色www| 欧美激情亚洲国产| 久热精品视频在线| 欧美日韩国产第一页| 久久成人精品电影| 国产精品久久久久久久久久小说| 高清日韩电视剧大全免费播放在线观看| 亚洲美女av网站| 国产精品久久久久久久av大片| 久久精视频免费在线久久完整在线看| 久久精品国产成人精品| 国产精品九九久久久久久久| 日韩免费观看网站| 日韩美女av在线| 98精品国产高清在线xxxx天堂| 国产伊人精品在线| 亚洲国模精品一区| 这里精品视频免费| 高清欧美电影在线| 亚洲福利在线看| 亚洲一区二区在线| 亚洲国产日韩一区| 91精品视频在线播放| 国产亚洲欧美日韩一区二区| 国产精品高清免费在线观看| 国产一区二区三区视频免费| 欧美精品日韩www.p站| 97香蕉超级碰碰久久免费的优势| 日韩在线欧美在线国产在线| 精品视频偷偷看在线观看| 国产精品三级美女白浆呻吟| 欧美丝袜美女中出在线| 久久久国产一区二区| 国产精品美女呻吟| 一本大道亚洲视频| 国产成人中文字幕| 日韩美女在线观看| 国产精品久久久久av免费| 欧美视频在线视频| 久久理论片午夜琪琪电影网| 欧美重口另类videos人妖| 一本色道久久88亚洲综合88| 国产美女精品免费电影| 久久久久久久亚洲精品| 秋霞av国产精品一区| 欧美孕妇孕交黑巨大网站| 91sa在线看| 高清一区二区三区日本久| 欧美精品日韩www.p站| 91麻豆国产精品| 国产一区二区在线免费视频| 亚洲香蕉在线观看| 97免费视频在线| 77777亚洲午夜久久多人| 亚洲免费视频一区二区| 欧美日韩午夜激情| 欧美激情综合色综合啪啪五月| 欧美性生交大片免网| 久久久人成影片一区二区三区| 久久精品一偷一偷国产| 俺去了亚洲欧美日韩| 亚洲人成啪啪网站| 国产丝袜一区二区三区免费视频| 欧美夜福利tv在线| 亚洲男人天堂网| 午夜精品在线视频| 欧美国产激情18| 欧美久久精品午夜青青大伊人| 欧美最猛性xxxx| 国内精品久久久久久| 国产日韩精品入口| 亚洲欧美另类国产| 久久久久久久久久婷婷| 97超碰蝌蚪网人人做人人爽| 日韩av在线一区| 九九九久久国产免费| 成人羞羞国产免费| 精品亚洲永久免费精品| 欧美伦理91i| 亚洲精品在线不卡| 日韩av不卡在线| 欧美人与性动交a欧美精品| 亚洲欧美精品一区| 日本精品在线视频| 欧美日韩成人在线播放| 久久亚洲春色中文字幕| 国内精品一区二区三区四区| 欧美成人中文字幕| 亚洲欧美精品在线| 全亚洲最色的网站在线观看| 国产视频久久久久| 一区二区国产精品视频| 热久久免费国产视频| 久久精品视频免费播放| 久久亚洲影音av资源网| 国产美女主播一区| 日韩欧美黄色动漫| 国产精品免费电影| 亚洲精品成人久久电影| 欧美成人在线影院| 亚洲精品98久久久久久中文字幕| 国产成人一区二区三区小说| 欧美中文在线免费| 国产精品男人的天堂| 国产精品美女999| 久久久在线观看| 伊人一区二区三区久久精品| 成人妇女淫片aaaa视频| 亚洲精品久久久久中文字幕欢迎你| 欧美一级高清免费| 亚洲日本成人女熟在线观看| 精品亚洲国产视频| 久久久久久久久久久久久久久久久久av| 国产精品jizz在线观看麻豆| 欧美精品激情视频| 欧美精品在线免费播放| 亚洲天堂一区二区三区| 国产精品亚洲美女av网站| 欧美午夜精品久久久久久久| 俺去啦;欧美日韩| 亚洲国产精品久久精品怡红院| 黄色一区二区三区| 精品国产电影一区| 欧美与黑人午夜性猛交久久久| 日韩欧美在线字幕| 国产91精品久久久| 亚洲石原莉奈一区二区在线观看| 亚洲精品欧美一区二区三区| 国产成人av网址| 日韩在线不卡视频| 精品国产一区二区在线| 国产精品三级美女白浆呻吟| 国产999在线| 久久精品99久久香蕉国产色戒| 久久精品久久久久| 日韩欧美在线视频免费观看| 57pao成人永久免费视频| 国产精品丝袜一区二区三区| 日韩精品极品毛片系列视频| 日韩精品免费在线视频| 亚洲理论电影网| 国产www精品| 欧美一区二粉嫩精品国产一线天| 国产欧美精品va在线观看| 亚洲伊人一本大道中文字幕| 欧美激情区在线播放| 欧美一级视频免费在线观看| 国产精品免费视频xxxx| 狠狠躁夜夜躁人人躁婷婷91| 亚洲国产精品一区二区三区| 91精品久久久久久久久久另类| 亚洲精品国精品久久99热| 91国偷自产一区二区三区的观看方式| 久久99视频精品| 日韩美女av在线| 91九色综合久久| 欧美日韩精品在线观看| 中文字幕亚洲色图| 国产亚洲美女久久| 亚洲国产中文字幕久久网| 亚洲天堂av在线播放|