關於C#非同步編程你應該瞭解的幾點建議

来源:https://www.cnblogs.com/yilezhu/archive/2019/12/26/12099219.html
-Advertisement-
Play Games

前段時間寫了一篇關於C 非同步編程入門的文章,你可以點擊《 "C 非同步編程入門看這篇就夠了" 》查看。這篇文章我們來討論下關於C 非同步編程幾個不成文的建議,希望對你寫出高性能的非同步編程代碼有所幫助。註:本文的很多內容都是學習《Effective C 》的總結。 作者:依樂祝 原文地址:https:// ...


前段時間寫了一篇關於C#非同步編程入門的文章,你可以點擊《C#非同步編程入門看這篇就夠了》查看。這篇文章我們來討論下關於C#非同步編程幾個不成文的建議,希望對你寫出高性能的非同步編程代碼有所幫助。註:本文的很多內容都是學習《Effective C#》的總結。

作者:依樂祝

原文地址:https://www.cnblogs.com/yilezhu/p/12099219.html

儘量不要編寫返回值類型為void的非同步方法

在通常情況下,建議大家不要編寫那種返回值類型為void的非同步方法,因為這樣做會破壞該方法的啟動者與方法本身之間的約定,這套約定本來可以確保主調方能夠捕獲到非同步方法所發生的異常。
正常的非同步方法是通過它返回的Task對象來彙報異常的。如果執行過程中發生了異常,那麼Task對象就進入了faulted(故障)狀態。主調方在對非同步方法所返回的Task對象做await操作時,該對象若已處在faulted狀態,系統則會將執行非同步方法的過程中所發生的異常拋出,反之,若Task尚未執行到拋出異常的那個地方,則主調方的執行進度會暫停在await語句這裡,等系統稍後安排某個線程繼續執行該語句下方的那些代碼時,異常才會拋出。

總結一句話就是:void的非同步方法發生異常時,開發者得不到任何通知,程式既不會觸發普通的異常處理程式,也不會把這些異常記錄下來。總之,這會讓相關的線程默默的終止掉。

不要把同步方法與非同步方法組合起來使用

async關鍵字來修飾的方法意味著該方法有可能會在執行完所有工作之前就把控制權返回給主調方,而且,它返回給主調方的是個代表工作進度的Task對象。主調方可以查詢此對象的狀態,以瞭解該工作是否已經完成、尚未完成還是在執行過程中發生了故障。此外,這種方法還在暗示主調方:本方法所執行的工作可能要花費很長時間,因此建議你先去做其他一些事情,稍後再來向我索要結果。
與此相反,如果把某個方法設計成同步方法,那麼意味著當該方法執行完畢時,它的後置條件必定能夠得到滿足。無論這個方法要花多長時間去完成工作,它都會採用與主調方相同的資源來完成,主調方必須等這個方法徹底執行完畢才能向下執行。
這兩種方法單獨寫起來都很清晰,但是如果把他們組合在一起就會讓方法變得十分難用,而且有可能導致各種bug,如死鎖。因此,這裡提出兩條重要的原則。第一,不要讓同步方法必須等待非同步方法執行完畢才能往下執行(儘量不用Wait()以及.result這些阻塞式的方法)。第二,不要讓非同步方法把雖然耗時很長、計算量很大但是完全可以由自己執行的工作轉交給另一個非同步任務去做。’
當然對於第二點,這並不是說計算量較大的任務絕對不能放在單獨的線程中執行,而是說不應該把只用一個線程就能迅速做好的任務刻意的拆解成許多個較小的部分,並把他們分別放在多個新的線程上執行,而是應該把整個任務都交給某個線程來執行才對。

使用非同步方法時應儘量避免線程分配

非同步任務看上去好像很神奇,因為這種任務刻意轉移到另一個地方去做,使得開啟這項任務的非同步方法可以在該任務完成之後,從早前暫停的地方繼續往下推進。不過,要想發揮非同步任務的功效,就必須保證把這項任務交出去確實能夠少占用一些資源,而不是僅僅會在相似的資源之間進行上下文切換。
如:對於一個控制台程式,如果只是執行一項計算量較大且耗時較長的任務(或者說,運行時間較長的CPU密集型的任務),那麼把該任務單獨放在另一個線程中並沒有多大好處。因為這樣做只能讓工作線程始終處於繁忙狀態,而主線程則必須一直卡在那裡等待工作線程把任務做完。在這種情況下,實際上是用兩個線程來完成原本只需要一個線程就能做好的工作,造成了資源的浪費。

避免不必要的上下文切換

目前C#代碼中使用async以及await實現的非同步方法預設是把await之後的代碼放在早前捕獲的那個上下文中執行的,這是因為這樣做比較穩妥,它最多只會引發幾次無謂的上下文切換,而不會使程式出現重大的錯誤,與之相反,如果系統不把山下文切換回去,那麼萬一遇到的是只能在特定的上下文中才能執行的代碼,那麼程式就有可能崩潰。因此,無論有沒有必要切換上下文,系統都會切換至早前捕獲到的那個上下文,並把await之後的語句放在那個上下文執行。
如果不想讓系統做出這樣的安排,那麼可以調用ConfigureAwait()方法。這表示接下來的那些代碼無須放在早前捕獲的上下文中執行。例如在很多程式集中,await語句之後的那些代碼一般都與上下文無關,因此與,可以調用Task對象的ConfigureAwait()方法告訴系統,在執行完這項任務之後,不必專門把await下麵的代碼放在早前捕獲的上下文中運行。如下所示:

public static async Task<XElement> ReadPacket(string url)
{
    var result=await DownloadAsync(url)
                .ConfigureAwait(false);
    return XElement.Parse(result);          
}

C#語言預設讓程式把await下麵的語句都放在早前捕獲的上下文中執行,這樣做雖然較為安全,但是會降低程式的效率。因此為了讓用戶能夠更加順暢的使用程式,我們應該調整代碼的結構,把必須運行在特定上下文的代碼剝離出來,並儘量考慮在await語句那裡調用ConfigureAwait(false),使得程式可以把語句下麵的代碼放在預設上下文中運行,而不是切換回早前的上下文。

通過Task對象來進行非同步開發

Task(任務)是一種抽象機制,可以用來表示某項工作,於是,就能夠把該工作轉交給其他資源去完成。Task類型以及與之相關的類與結構體提供了豐富的API,讓開發者可以操控Task對象以及由該對象所表示的工作。此外,Task對象自身也具備一些方法與屬性,可以用來操作本對象所表示的任務。這些Task對象可以合起來構成一項比較大的任務,他們之間既能夠按照順序執行,也能夠平行的執行。
可以通過await語句來確保某些任務之間能夠按照一定的順序執行,也就是說,只有當該語句所要等待的那項工作完畢之後,語句下方的代碼才能夠執行。
總之,由於C#提供了一套豐富的API,因此可以寫出相當優雅的演算法來處理Task對象,並對這些對象所表示的任務進行安排。對任務的用法理解的越透徹,寫出來的非同步代碼越清晰。
這裡簡單說明兩個常用的API:

  1. WhenAll:會根據現有的一批任務創建出一項新的任務,只有當那批任務全部執行完畢時,這項新人物才能夠完成。對Task.WhenAll所返回的新任務進行await操作會獲得一份列表,早前的那些任務的執行結果就位於該列表中。
  2. WhenAny:為了儘早的獲得某個結果,可能啟動多項任務,使得他們分別從不同的途徑去獲取該結果。只要其中有一項任務完成,你的目標就達成了,針對這項需求,可以考慮使用Task.WhenAny方法,並把自己所創建的那批任務傳進去。對WhenAny方法所返回的Task對象進行await操作可以獲取到一項任務,它指的就是這批任務中最先執行完畢的那項任務。

考慮實現任務的取消協議

非同步任務的編程模型(也叫基於任務的非同步編程模型)提供了標準的API,用來取消任務或者廣播任務的執行進度。雖然這些API是可選的,但如果某項任務確實能夠彙報其進度,或者能夠予以取消,那就可以考慮用合適的辦法來實現這些API。

針對需要取消的任務,我們可以通過CanclelationTokenSource對象來進行取消操作。這種對象是一種起到中介作用的對象。該對象處在有可能發出取消請求的客戶代碼與支持取消功能的那項操作之間。

如果正在執行的任務發現客戶端想要取消該操作,那麼它就會通過ThrowIfCanclellationRequested()方法拋出TaskCanclledException異常,庸醫表示整個工作流程沒有能夠完全得到執行。

此外,返回值類型為void類型的非同步方法不應該支持取消功能。

緩存泛型非同步方法的返回值

可能你在進行非同步編程的時候對非同步方法設置的返回類型都是Task或者Task<T>,然而有些時候把返回值類型設為Task可能會影響性能。如果某個迴圈或某段代碼需要頻繁的運行,那麼系統就有可能分配很多個Task對象,從而占用相當多的資源。好在C#提供了一種新的類型,叫做ValueTask<T>對象,他用起來比普通的Task更為高效。該類型是值類型,因此創建這種類型的對象時,不需要再分配額外的空間。這個好處使得我們可以多創建一些這樣的對象,而不用擔心它會像Task對象那樣占據過多的資源。如果你的非同步方法可以根據早前緩存起來的結果直接返回相應的值,那麼尤其應該考慮把返回值類型設置為ValueTask<T>

其次,ValueTask提供了一個能夠接受Task參數的構造函數,這個構造函數會在其內部等候該Task的執行結果。

總結

今天分享的內容比較多,而且很多都比較難理解,不過確實是寫出高性能非同步方法所必須要掌握的技巧。由於時間較短,因此也沒來得及通過代碼進行講述,所以需要有一定的基礎才能看懂,不過還是希望對您有所幫助。


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

-Advertisement-
Play Games
更多相關文章
  • 追求極致,永臻完美 A Beautiful WPF Control UI 一款簡單漂亮的WPF UI,融合部分開源框架的組件,為個人定製的UI,可供學者參考。 Nuget 搜索"AduSkin" 可直接導包使用 具體使用請參考 AduSkin.Demo 如有 WPF UI 外包 可聯繫我 技術交流 ...
  • 下麵我們繼續學習C#的語法。結構struct,C#中的結構和我們PLC中建立的UDT(結構體)是一樣的。裡面存儲了相關的不同類型的數據。 有一句話我覺得十分重要:方法是依存於結構和對象存在的。這以後我們會個更加深入的學習的。 Struct結構: 可以幫助我們一次性聲明不同類型的變數。 語法: [pu ...
  • Quartz.Net使用教程 在項目的開發過程中,難免會遇見後需要後臺處理的任務,例如定時發送郵件通知、後臺處理耗時的數據處理等,這個時候你就需要Quartz.Net了。 Quartz.Net是純凈的,它是一個.Net程式集,是非常流行的Java作業調度系統Quartz的C#實現。 Quartz.N ...
  • Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems. ...
  • 一、創建winform工程 拖拽控制項Chart 二、比如要繪製倆條曲線,設置Chart控制項的屬性Series 三、chart的屬性根據自己的業務需求設計,我這裡只設置了圖標類型 代碼: using System; using System.Collections.Generic; using Sys ...
  • 在asp.net core 3.0 中,如果直接在 中返回 類型,會拋出如下錯誤: The collection type 'Newtonsoft.Json.Linq.JObject' is not supported. System.NotSupportedException: The colle ...
  • 對目前市場上的web列印方法做了些研究心得 1、比較大眾的IE組件有Lodop; 這種組件方式,需要IE內核的瀏覽器,對於非IE內核的瀏覽器,如Google Chrome、Mozilla Firefox、Safari、Opera、Netscape就不行了,限制了客戶只能用IE,IE都差不多要淘汰了, ...
  • 最近項目上開始使用.net core,新的項目,熟悉的東西比較多,現在花點時間來梳理一下,重頭開始搭建一個.net core項目。哈哈,這個相對老手來說,估計會覺得小兒科,沒事,也就當一次分享總結罷了,希望對有幫助的小伙伴有點幫助就好。 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...