.NET 實現並行的幾種方式(四)

来源:http://www.cnblogs.com/08shiyan/archive/2016/12/01/6093992.html
-Advertisement-
Play Games

本隨筆續接:.NET 實現並行的幾種方式(三) 八、await、async - 非同步方法的秘密武器 1) 使用async修飾符 和 await運算符 輕易實現非同步方法 前三篇隨筆已經介紹了多種方式、利用多線程、充分利用多核心CPU以提高運行效率。但是以前的方式在WebAPI和GUI系統上、 使用起來 ...


本隨筆續接:.NET 實現並行的幾種方式(三)


八、await、async - 非同步方法的秘密武器

1) 使用async修飾符 和 await運算符 輕易實現非同步方法

前三篇隨筆已經介紹了多種方式、利用多線程、充分利用多核心CPU以提高運行效率。但是以前的方式在WebAPI和GUI系統上、

使用起來還是有些繁瑣,尤其是在需要上下文的情況下。而await/async就是在這樣的情況下應運而生,並且它可以在理論上讓CPU跑到100%。

async修飾符:它用以修飾方法、lambda表達式、匿名方法,以標記方法為非同步方法。非同步方法必須遵循的規範如下:

1、返回值僅且僅有三種: void、Task、Task<T>.

2、方法參數不可以使用 ref、out類型參數。

await運算符:它用以標記一個系統可在其上恢復執行的掛起點。該運算符會告訴computer不會再往下繼續執行該方法、直到等待的非同步方法執行完畢為止。同時會將程式的控制權return給其調用者。await表達式不阻止正在執行它的線程。 而是讓編譯器將非同步方法剩餘部分註冊為等待任務的延續任務。 當等待任務完成時,它會調用其延續任務,如同在掛起點上恢復執行。

2)簡單Demo

// Three things to note in the signature:  
//  - The method has an async modifier.   
//  - The return type is Task or Task<T>. (See "Return Types" section.)  
//    Here, it is Task<int> because the return statement returns an integer.  
//  - The method name ends in "Async."  
async Task<int> AccessTheWebAsync()  
{   
    // You need to add a reference to System.Net.Http to declare client.  
    HttpClient client = new HttpClient();  
  
    // GetStringAsync returns a Task<string>. That means that when you await the  
    // task you'll get a string (urlContents).  
    Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");  
  
    // You can do work here that doesn't rely on the string from GetStringAsync.  
    DoIndependentWork();  
  
    // The await operator suspends AccessTheWebAsync.  
    //  - AccessTheWebAsync can't continue until getStringTask is complete.  
    //  - Meanwhile, control returns to the caller of AccessTheWebAsync.  
    //  - Control resumes here when getStringTask is complete.   
    //  - The await operator then retrieves the string result from getStringTask.  
    string urlContents = await getStringTask;  
  
    // The return statement specifies an integer result.  
    // Any methods that are awaiting AccessTheWebAsync retrieve the length value.  
    return urlContents.Length;  
}  
await/async demo

3)直觀的順序圖

該圖出自: https://msdn.microsoft.com/zh-cn/library/mt674882.aspx 

4) await async編程最佳做法

1、非同步方法儘量少用 void類型返回值、替代方案 使用Task類型,特例:非同步事件處理函數使用void類型

原因1、async void 無法使用try ... catch進行異常捕獲,它的異常會在上下文中引發。捕獲該種異常的方式為在GUI或web系統中使用

AppDomain.UnhandledException 進行全局異常捕獲。對於需要進隊異常進行處理的地方、這將是個災難。

原因2、async void 方法、不可以“方便”的知道其什麼時候完成,這對於超過50%的非同步方法而言、將是滅頂之災。而 async Task

可以配合 await、await Task.WhenAny、await Task.WhenAll、await Task.Delay、await Task.Yield 方便的進行後續的任務處理工作。

特例、因為事件本身是不需要返回值的,並且事件的異常也會在上下文中引發、這是合理的。所以非同步的事件處理函數使用void類型。

 

2、推薦一直使用async,而不要混合使用阻塞和非同步(async)避免死鎖, 特例:Main方法

使用混合編程的死鎖demo

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();
  }
}
DeadlockDemo

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

特例:Main方法是不可用async修飾符進行修飾的(編譯不通過)。

執行以下操作… 阻塞式操作… async的替換操作
檢索後臺任務的結果 Task.Wait 或 Task.Result await
等待任何任務完成 Task.WaitAny await Task.WhenAny
檢索多個任務的結果 Task.WaitAll await Task.WhenAll
等待一段時間 Thread.Sleep await Task.Delay

 

 

 

 

 

3、如何可以,請用ConfigureAwait 忽略上下文

上文也說過了,當非同步任務完成後、它會嘗試在之前的上下文環境中恢復執行。這樣帶來的問題是時間片會被切分成更多、造成更多的線程調度上的性能損耗。

一旦時間片被切分的過多、尤其是在GUI和Web具有上下文環境中運行,影響會更大。

另外,使用ConfigureAwait忽略上下文後、可避免死鎖。 因為當等待完成時,它會嘗試線上程池上下文中執行 async 方法的剩餘部分,不會存線上程等待。

 

5)疑問:關於 await的使用次數 和 使用的線程數量 之間的關係

使用一個await運算符,就一定會使用一個新的線程嗎? 答案:不是的。

前文已經介紹過,await運算符是依賴Task完成非同步的、並且將後續代碼至於Task的延續任務之中(這一點是編譯器搞得怪、生成了大量的模板代碼來實現該功能)。

因此,編譯器以await為分割點,將前一部分的等待任務和後一部分的延續任務分割到兩個線程之中。

前一部分的等待任務:該部分是Task依賴調度器(TaskScheduler)、從線程池中分配的工作線程。

而後一部分的延續任務:該部分所運行的線程取決於兩點:第一點,Task等待任務在運行之前捕獲的上下文環境,第二點:是否使用ConfigureAwait (false) 

忽略了之前捕獲的上下文。如果沒有忽略上下文並且之前捕獲的上下文環境為:SynchronizationContext(即 GUI UI線程 或 Web中具有HttpContext的線程環境)

則 延續任務繼續在 SynchronizationContext 上下文環境中運行,否則 將使用調度器(TaskScheduler)從線程池中獲取線程來運行。

 

另外註意:調度器從線程池中獲取的線程、並不一定是新的,即使在迴圈連續使用多次(如果任務很快完成),那麼也有可能多次都使用同一個線程。

測試demo:

        /// <summary>
        /// 在迴圈中使用await, 觀察使用的線程數量
        /// </summary>
        /// <returns></returns>
        public async Task ForMethodAsync()
        {
            // 休眠
            // await Task.Delay(TimeSpan.FromMilliseconds(100)).ConfigureAwait(false);
            // await Task.Delay(TimeSpan.FromMilliseconds(100));

            for (int i = 0; i < 5; i++)
            {
                await Task.Run(() =>
                {
                    // 列印線程id
                    PrintThreadInfo("ForMethodAsync", i.ToString());
                });
            }            
        }
在迴圈中使用await, 觀察使用的線程數量

上述demo在運行多次後,可能會得到上述結果:5次迴圈使用的是同一個線程,線程id為16,UI線程id為10。

 

結論:await的使用次數 大於 使用的線程數量,也有可能、多次使用await 只會 使用一個線程。

 

6)await/async 的缺點

1、由於編譯在搞怪、會生成大量的模板代碼、使得單個非同步方法 比 單個同步方法 運行得要慢,與之相對應的獲取到的性能優勢是、充分利用了多核心CPU,提高了任務併發量。

2、掩蓋了線程調度、使得系統開發人員無意識的忽略了該方面的性能損耗。

3、如果使用不當,容易造成死鎖

 

 

附,Demo : http://files.cnblogs.com/files/08shiyan/ParallelDemo.zip

參見更多:隨筆導讀:同步與非同步


(未完待續...)

 


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

-Advertisement-
Play Games
更多相關文章
  • 示例代碼: is a way to use packages that were not designed for that framework. Basically you tell it "Use those targets even though they don't seem to be s ...
  • 在C#中有多個線程同時對某個變數進行操作的時候,我們應該使用原子操作,防止多線程取到的值不是最新的值。 本文介紹了多線程中使用原子操作與不使用原子操作的不同,以及使用原子操作與使用lock鎖來保障多線程執行的唯一性的性能優劣。 ...
  • 在微軟官方關於ef7的介紹中強調,ef7將捨棄database first、model first,只保留code first的使用。這引起了很多人的擔憂,擔憂源自對code first的錯誤理解。因為很多人認為code first是區別於database first與model first的第三種 ...
  • 在目前的軟體項目中,都會較多的使用到對文檔的操作,用於記錄和統計相關業務信息。由於系統自身提供了對文檔的相關操作,所以在一定程度上極大的簡化了軟體使用者的工作量。 在.NET項目中如果用戶提出了相關文檔操作的需求,開發者較多的會使用到微軟自行提供的插件,在一定程度上簡化了開發人員的工作量,但是同時也 ...
  • 1.設置gridview裡面的屬性中ShowFooter="True",就是把gridview的頁腳顯示出來 this.gvData.OptionsView.ShowFooter = true; 2.設置要彙總的列,例如彙總"ReceiveMoney"金額列 3.給gridView添加CustomS ...
  • 假設有如下代碼所示的多線程: 這個新建的線程t在執行完Test()方法後會自動銷毀嗎?還是需要寫代碼手動銷毀呢? 下麵就多線程的非主線程銷毀機製做個總結: 1).t結束就自動銷毀了 2).設置線程屬性IsBackground=true 將線程t作為後臺線程,隨著主線程結束而一起結束,不管這個線程有沒 ...
  • 最近一直在研究Paypal的支付平臺,因為本人之前沒有接觸過介面這一塊,新來一家公司比較不清楚流程就要求開發兩個支付平臺一個是支付寶(這邊就不再這篇文章裡面贅述了),但還是花了2-3天的時間通過自己研究和借鑒別人的文章以及強大的Paypal官方技術文檔搞清楚了真正的原理和開發過程。(如有不同見解或者 ...
  • 上個月月底,VS2017RC版發佈了,一個很大的特點就是將原來的xProj文件又改回了csproj了。 這樣一改,其實很多新的問題也暴露出來了,最嚴重的問題就是Net版本相容性。 原來的Net體系大致是NetFramework,Net Core這樣的,雖然也有Net Standard 這樣的概念,但 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...