C#併發編程之非同步編程(二)

来源:https://www.cnblogs.com/edison0621/archive/2019/03/23/10584279.html
-Advertisement-
Play Games

寫在前面 前面一篇文章介紹了非同步編程的基本內容,同時也簡要說明瞭async和await的一些用法。本篇文章將對async和await這兩個關鍵字進行深入探討,研究其中的運行機制,實現編碼效率與運行效率的提升。 非同步方法描述:使用async修飾符來標識一個方法或Lambda表達式的,被稱之為非同步方法。 ...


寫在前面

前面一篇文章介紹了非同步編程的基本內容,同時也簡要說明瞭async和await的一些用法。本篇文章將對async和await這兩個關鍵字進行深入探討,研究其中的運行機制,實現編碼效率與運行效率的提升。


非同步方法描述:使用async修飾符來標識一個方法或Lambda表達式的,被稱之為非同步方法。

非同步方法編譯:編譯器在遇到await表達式後會截斷方法,並將剩餘的非同步方法註冊為在等待任務完成後需要繼續執行的後續部分。

非同步方法基礎及其運行流程

Async和Await

非同步方法使用async修飾,該方法包含一個或多個await表達式或語句,方法同步運行,直至到達第一個 Await,此時暫停,直到等待的任務完成,在任務完成後,控制權返回給方法的調用方。如果方法中並不包含await,則該方法不會像同步方法一樣被掛起。

非同步方法通常包含await運算符的一個或多個實例,但缺少await表達式也不會導致生成編譯器錯誤,之會因為沒有await而發出警告,但編譯依然通過。

非同步方法使用await關鍵字來確定等待位置,但await表達式並不阻止正在執行到此位置的線程,也就是說非同步方法在await表達式執行時只是暫停,並不會導致方法退出,只會導致finally代碼塊不運行。非同步方法只有在等待的任務完成後,才能通過該位置並繼續執行剩下的邏輯,控制權也在此處返回給非同步方法的調用方。

如果非同步方法未使用Await運算符標記暫停點,那麼非同步方法會作為同步方法執行,即使有Async修飾符,也不例外。如以下示例

   1:  public async static Task<string> GetUserInfoAsync()
   2:  {
   3:      User user = await db.User.FirstOrDefaultAsync();//此處會掛起
   4:   
   5:      Task<User> user = db.User.FirstOrDefaultAsync();//此處不會掛起,註意此處,返回值也變了,接下來會討論一下非同步方法的返回值
   6:   
   7:      return string.Empty;
   8:  }
 
具MSDN描述,aysnc關鍵字是一個非保留的關鍵字。 在修飾方法或 lambda 表達式時,它是關鍵字,await也作為關鍵字存在。 在所有其他上下文中,async和await都會將其解釋為標識符。不過開發人員可以不用太過關註這段,只需要知道aysnc會將一個方法標識成非同步方法,而await可以掛起非同步方法的執行即可。

關鍵點

1、和被async修飾的方法不一樣,如果方法中含有await關鍵字,方法必須使用async標識符,否則編譯不通過。

2、在非同步編程過程中,比較推薦的做法是,被標記了async關鍵字的非同步方法應該包含至少一個await表達式或語句。

3、非同步方法的命名以Async結尾 

非同步返回類型和異常處理

需要說明的是,本文所討論的非同步方法指的是基於任務的非同步編程模型,返回值是,Task或Task<TResult>。

1、如果方法需要返回string類型,那麼將返回Task<string>。如果方法沒有指定返回類型,那麼將返回Task。每個返回的任務都表示正在進行的工作,任務封裝有關非同步進程狀態的信息,如果未成功,則會引發異常。非同步方法返回 Task 或 Task<TResult>。 返回任務的屬性攜帶有關其狀態和歷史記錄的信息,如任務是否完成、非同步方法是否導致異常或已取消以及最終結果是什麼。 可使用await運算符訪問這些屬性。

 
   1:  public async static Task<User> GetUserInfoAsync()
   2:  {
   3:      User user = await db.User.FirstOrDefautAsync();
   4:   
   5:      return user;
   6:  }

2、如果等待的任務返回非同步方法導致異常,則 await 運算符會以同步方式拋出異常。如果等待的返回任務的非同步方法取消,await運算符引發OperationCanceledException。如果非同步方法中沒有使用await阻塞,可以使用try-catch捕捉異常,只是異常發生的時機可能會滯後。

非同步方法的運行流程

瞭解非同步方法的運行機制,就是要瞭解非同步編程中的控制流是如何一步步執行的。如果需要詳細瞭解控制流,可以非同步到MSDN中查看。

下圖及其描述摘自MSDN

navigation-trace-async-program

關係圖中的數值對應於以下步驟。

  1. 事件處理程式調用並等待 AccessTheWebAsync 非同步方法。

  2. AccessTheWebAsync 創建HttpClient實例並調用GetStringAsync非同步方法,獲取的內容字元串方式返回。

  3. GetStringAsync 中發生了某種情況,該情況掛起了它的進程。 可能必須等待其他阻止任務完成。 為避免阻止資源,GetStringAsync 會將控制權出讓給其調用方 AccessTheWebAsyncGetStringAsync 返回Task<TResult>,其中 TResult 為字元串,並且 AccessTheWebAsync 將任務分配給 getStringTask 變數。 該任務將調用GetStringAsync正在進行的進程,在調用完成時產生返回字元串給urlcontent。

  4. 由於尚未等待 getStringTask,因此,AccessTheWebAsync 可以繼續執行而不依賴於 GetStringAsync 最終結果的完成。 該任務繼續調用同步方法 DoIndependentWork

  5. DoIndependentWork 作為一個同步方法,在自身工作完成後返回到調用方。

  6. AccessTheWebAsync 已運行完畢,可以不受 getStringTask 的結果影響。 接下來,AccessTheWebAsync 需要計算並返回已下載的字元串的長度,但該方法只有在獲得字元串的情況下才能計算該值。

    因此,AccessTheWebAsync 使用一個 await 運算符來掛起其任務,並把控制權交給調用 AccessTheWebAsync 的事件處理程式。 AccessTheWebAsyncTask<int>返回給調用方。 該任務將計算下載字元串長度。 

  7. GetStringAsync 完成並生成一個字元串結果。 字元串結果不是通過按你預期的方式調用 GetStringAsync 所返回的。 (記住,該方法已返回步驟 3 中的一個任務)。相反,字元串結果存儲在表示 getStringTask 方法完成的任務中。 await 運算符從 getStringTask 中檢索結果。 賦值語句將檢索到的結果賦給 urlContents

  8. AccessTheWebAsync 獲取字元串結果時,該方法可以計算字元串長度。 然後,AccessTheWebAsync 工作也將完成,並且等待事件處理程式的繼續使用。 事件處理程式也將最終獲得字元串的長度信息。

註意:

如果 GetStringAsync(因此 getStringTask)在 AccessTheWebAsync 等待前完成,則控制權會保留在 AccessTheWebAsync中。 如果非同步調用過程 (AccessTheWebAsync) 已完成,並且 AccessTheWebSync 不必等待最終結果,則掛起然後返回到 getStringTask 將造成資源浪費。

在調用方內部(此示例中的事件處理程式),處理模式將繼續。 在等待結果前,調用方可以開展不依賴於 AccessTheWebAsync 結果的其他工作,否則就需等待片刻。 事件處理程式等待 AccessTheWebAsync,而 AccessTheWebAsync 等待 GetStringAsync。

非同步編程對性能的影響

 在.NET非同步編程中,async和await不會創建其他線程。同時非同步方法不會在其自身線程上運行,因此它不需要多線程。只有當方法處於活動狀態時,該方法將在當前同步上下文中運行並使用線程上的時間。可以使用Task.Run將占用大量CPU的工作移到後臺線程,但是後臺線程不會幫助正在等待結果的進程變為可用狀態。

對於非同步編程而言,基於非同步的方法優於幾乎每個用例中的現有方法。具體而言,這種方法優於BackgroundWorker的I/O綁定操作因為代碼更簡單且無需防止爭用條件。結合Task.Run使用時,非同步編程比BackgroundWorker更適用於CPU綁定的操作,因為非同步編程將運行代碼的協調細節與Task.Run傳輸至線程池的工作區分開來。

那麼非同步編程對線程的影響又是什麼呢,相比大家應該都知道,ASP.NET中有兩類線程,工作線程,和IO線程。

其中工作線程處理普通請求的線程,也是我們用得最多的線程。這個線程是有限的,是根CPU的個數相關的。IO線程,比如與文件讀寫,網路操作等是可以非同步實現並且使性能提升的地方。I/O線程通常情況下是空閑的。所以可以使用IO線程來代替工作線程,一方面充分運用了系統資源,另一方面也節省了工作線程調度及切換所帶來的損耗。

由此我們需要明白,在I/O密集型處理時,使用非同步可以帶來很大的提升,比如資料庫操作以及網路操作。

即便非同步編程帶來性能的提升,但是運用不慎,也會對系統性能產生反作用,比如直接使用Task.Run或者Task.Factory.StartNew所帶來的非同步編程,這些方式會占用工作線程以及工作線程之間的切換。 

非同步編程需要註意的地方


1、同時async和await侵入性或者傳遞性很強,所有調用的地方都需要同步使用async和await,這對系統中老代碼的修改產生了很大的影響。

2、非同步編程中無法使用lock鎖,因為非同步方法不會在自身線程上運行,lock就變成了多餘的了。但非同步編程場景下可以使用AsyncLock鎖,對相應的代碼進行鎖定。

3、非同步編程里,比較推薦的做法是避免上線文延續,此處不再做更多說明,參考我的前一篇文章《非同步編程(一)》

4、非同步編程是否真的提升了系統性能,目前來看大多數場景下是提升了,尤其在I/O操作比較密集的業務場景下,比如查詢資料庫和HttpClient調用。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 面試題 如果讓你寫一個消息隊列,該如何進行架構設計?說一下你的思路。 面試官心理分析 其實聊到這個問題,一般面試官要考察兩塊: 你有沒有對某一個消息隊列做過較為深入的原理的瞭解,或者從整體瞭解把握住一個消息隊列的架構原理。 看看你的設計能力,給你一個常見的系統,就是消息隊列系統,看看你能不能從全局把 ...
  • 為了給列表框配備滾動條,看來很多別人的博客 終於解決了問題 ,現在我總結一下 效果圖 關鍵在標記紅色的兩句,為了讓兩個控制項相互配合,兩個控制項都得設置 lb.config(yscrollcommand=scr.set) 列表框換“視角”後 更新的滾動條狀態scr.config(command=lb.y ...
  • 從0到1:使用Caliburn.Micro(WPF和MVVM)開發簡單的計算器 之前時間一直在使用Caliburn.Micro這種應用了MVVM模式的WPF框架做開發,是時候總結一下了。 Caliburn.Micro(Caliburn.Micro框架概述 https://blog.csdn.net/ ...
  • 需要安裝nuget包Microsoft.Packaging.Tools.Trimming然後利用如下命令發佈dotnet publish -r win10-x64 -c release --self-contained true /p:TrimUnusedDependencies=true發佈完成之 ...
  • .NET Core 3.0 是 .NET Core 平臺的下一主要版本。本文回顧了 .Net Core 發展歷史,並展示了它是如何從基本支持 Web 和數據工作負載的版本 1,發展成為能夠運行 Web、桌面、機器學習、容器、IoT 等的版本 3.0。 .NET Core 1 .NET Core 的歷 ...
  • 前面介紹了DSAPI多功能組件編程應用-HTTP監聽服務端與客戶端的內容,這裡介紹一個適用於更高效更快速的基於HTTP監聽的服務端、客戶端。 在本篇,你將見到前所未有的超簡化超傻瓜式的HTTP監聽服務,與前篇中的不同,在DSAPI中,指令版同時包含了服務端與客戶端。 先來看一下使用方法,幾乎不需要太 ...
  • 文件下載是很多網站中含有的常用功能,在ASP.NET中可以使用FileStream類、HttpRequest對象、HttpResponse對象相互結合,實現輸出硬碟文件的功能。該方法支持大文件、續傳、速度限制、資源占用小。 FileStream類:MSDN上的解釋為,FileStrem類對文件系統上 ...
  • 事件:定義了事件成員的類允許通知其他其他對象發生了特定的事情。具體的說,定義了事件成員的類能提供以下功能 1.方法能登記它對事件的關註 2.方法能註銷它對事件的關註 3.事件發生時,登記了的方法將收到通知 類型之所以能提供事件通知功能,是因為類型維護了一個已登記方法的列表。事件發生後,類型將通知列表 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...