[.NET] 怎樣使用 async & await 一步步將同步代碼轉換為非同步編程

来源:http://www.cnblogs.com/liqingwen/archive/2016/11/22/6079707.html
-Advertisement-
Play Games

怎樣使用 async & await 一步步將同步代碼轉換為非同步編程 【博主】反骨仔 【出處】http://www.cnblogs.com/liqingwen/p/6079707.html 序 上次,博主通過《利用 async & await 的非同步編程》該篇點睛之作介紹了 async & awai ...


怎樣使用 async & await 一步步將同步代碼轉換為非同步編程

【博主】反骨仔    【出處】http://www.cnblogs.com/liqingwen/p/6079707.html   

  上次,博主通過《利用 async & await 的非同步編程》該篇點睛之作介紹了 async & await 的基本用法及非同步的控制流和一些其它的東西。

  今天,博主打算從創建一個普通的 WPF 應用程式開始,看看如何將它逐步轉換成一個非同步的解決方案

 

目錄

 

介紹

  這裡通過一個普通的 WPF 程式進行講解:

  只是一個文本框和一個按鈕,左邊文本框的內容為點擊右鍵按鈕時所產生的結果。

 

添加引用

  demo 可能需要用到的部分 using 指令:

using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading;

 

先創建一個同步的 WPF

  1.這是右邊點擊按鈕的事件:

 1         /// <summary>
 2         /// 點擊事件
 3         /// </summary>
 4         /// <param name="sender"></param>
 5         /// <param name="e"></param>
 6         private void btnSwitch_Click(object sender, RoutedEventArgs e)
 7         {
 8             //清除文本框所有內容
 9             tbResult.Clear();
10 
11             //統計總數
12             SumSizes();
13         }

  

  2.我在 SumSizes 方法內包含幾個方法:

    ① InitUrlInfoes:初始化 url 信息列表;

    ② GetUrlContents:獲取網址內容;

    ③ DisplayResults:顯示結果。

 

  (1)SumSizes 方法:統計總數。

 1         /// <summary>
 2         /// 統計總數
 3         /// </summary>
 4         private void SumSizes()
 5         {
 6             //載入網址
 7             var urls = InitUrlInfoes();
 8 
 9             //位元組總數
10             var totalCount = 0;
11             foreach (var url in urls)
12             {
13                 //返回一個 url 內容的位元組數組
14                 var contents = GetUrlContents(url);
15 
16                 //顯示結果
17                 DisplayResults(url, contents);
18 
19                 //更新總數
20                 totalCount += contents.Length;
21             }
22 
23             tbResult.Text += $"\r\n         Total: {totalCount}, OK!";
24         }
View Code

 

  (2)InitUrlInfoes 方法:初始化 url 信息列表。

 1         /// <summary>
 2         /// 初始化 url 信息列表
 3         /// </summary>
 4         /// <returns></returns>
 5         private IList<string> InitUrlInfoes()
 6         {
 7             var urls = new List<string>()
 8             {
 9                 "http://www.cnblogs.com/",
10                 "http://www.cnblogs.com/liqingwen/",
11                 "http://www.cnblogs.com/liqingwen/p/5902587.html",
12                 "http://www.cnblogs.com/liqingwen/p/5922573.html"
13             };
14 
15             return urls;
16         }
View Code

 

  (3)GetUrlContents 方法:獲取網址內容。

 1 /// <summary>
 2         /// 獲取網址內容
 3         /// </summary>
 4         /// <param name="url"></param>
 5         /// <returns></returns>
 6         private byte[] GetUrlContents(string url)
 7         {
 8             //假設下載速度平均延遲 300 毫秒
 9             Thread.Sleep(300);
10 
11             using (var ms = new MemoryStream())
12             {
13                 var req = WebRequest.Create(url);
14 
15                 using (var response = req.GetResponse())
16                 {
17                     //從指定 url 里讀取數據
18                     using (var rs = response.GetResponseStream())
19                     {
20                         //從當前流中讀取位元組並將其寫入到另一流中
21                         rs.CopyTo(ms);
22                     }
23                 }
24 
25                 return ms.ToArray();
26             }
27 
28         }
View Code

 

  (4)DisplayResults 方法:顯示結果

 1         /// <summary>
 2         /// 顯示結果
 3         /// </summary>
 4         /// <param name="url"></param>
 5         /// <param name="content"></param>
 6         private void DisplayResults(string url, byte[] content)
 7         {
 8             //內容長度
 9             var bytes = content.Length;
10 
11             //移除 http:// 首碼
12             var replaceUrl = url.Replace("http://", "");
13 
14             //顯示
15             tbResult.Text += $"\r\n {replaceUrl}:   {bytes}";
16         }
View Code

 

測試結果圖

   全部顯示需要耗費數秒的時間。在點擊的同時,它在等待請求資源下載時,UI 線程阻塞。因此,在點擊“啟動”按鈕後,將無法移動,最大化,最小化,甚至關閉顯示視窗。直到結果顯示之前這些操作都會沒有效果。如果網站沒有響應時,您沒有站點失敗的表示形式。即使不想再繼續等待並關閉程式都會很困難。

 

將上面的 demo 逐步轉換為非同步方法

  1.GetUrlContents 方法 => GetUrlContentsAsync 非同步方法

  (1) 將 GetResponse 方法改成 GetResponseAsync 方法:

  //var response = req.GetResponse();
  var response = req.GetResponseAsync()

  

  (2)在 GetResponseAsync 方法前加上 await:

  GetResponseAsync 將返回 Task。 在這種情況下,任務返回變數 TResult,具有類型 WebResponse

  從任務若要檢索 WebResponse 值,將 await 運算符應用於調用的 GetResponseAsync 方法。

  //var response = req.GetResponseAsync()
  var response = await req.GetResponseAsync()

  await 運算符掛起當前方法,直到等待的任務完成。同時,控制權返回到當前方法的調用方。在這裡,當前方法是 GetUrlContents,因此,調用方是 SumSizes。當任務完成時,將提交的 WebResponse 對象生成,將等待的任務的值分配給 response。

  上面的內容也可以拆分成下麵的內容:

  //Task<WebResponse> responseTask = req.GetResponseAsync();
  //var response = await responseTask;

   responseTask 為 webReq.GetResponseAsync 的調用返回 Task 或 Task<WebResponse>。 然後 await 運算符應用於 task 檢索 WebResponse 值。

  

  (3)由於在上一步中添加了 await 運算符,編譯器會報告錯誤。await 運算符在標有 async 的方法下才能使用。當您重覆轉換步驟替換 CopyTo 為 CopyToAsync 時,請先暫時忽略該錯誤。

  • 更改調用 CopyToAsync方法的名稱。

  • CopyTo 或 CopyToAsync 方法複製位元組為其參數,不返回有意義的值。在同步版本中,CopyTo 的調用不返回值。在非同步版本中,即CopyToAsync,返回 Task,可應用 await 於方法 CopyToAsync。

  //rs.CopyTo(ms);
  await rs.CopyToAsync(ms);

   

  (4)也要修改 Tread.Sleep。Thread.Sleep 是同步延遲,Task.Delay 非同步延遲;Thread.Sleep 會阻塞線程,而Task.Delay 不會。

  //Thread.Sleep(300);
  await Task.Delay(300);

   

  (5)在 GetUrlContents 仍然要修改的只是調整方法簽名。在標有非同步的方法只能使用 await 運算符 async 修飾符。添加 async 修飾符標記方法作為非同步方法 。

  //private async byte[] GetUrlContents(string url)
  //private async Task<byte[]> GetUrlContents(string url)
  private async Task<byte[]> GetUrlContentsAsync(string url)

  非同步方法的返回類型只能 Task<T>、Task 或 void。 通常 void 的返回類型僅在非同步事件處理程式中使用在某些情況下,您使用 Task<T>,如果返回類型 T 的值的完整方法具有 return 語句以及使用 Task,但是已完成方法不返回有意義的值。可以將 Task 返回類型理解為“任務 (失效)”。

  方法 GetURLContents 具有返回語句,因此,該語句返回位元組數組。 這裡,非同步版本的返回類型為 Task<T>,T 為位元組數組。在方法簽名中進行以下更改:

  • 返回類型更改 Task<byte[]>。

  • 按照約定,非同步方法是以“Async”結尾的名稱,因此可對方法 GetURLContentsAsync 重命名。

 

  (6)這是修改後的整體方法

 1         /// <summary>
 2         /// 獲取網址內容
 3         /// </summary>
 4         /// <param name="url"></param>
 5         /// <returns></returns>
 6         /// <remarks>
 7         /// private async byte[] GetUrlContents(string url)
 8         /// private async Task<byte[]> GetUrlContents(string url)
 9         /// </remarks>
10         private async Task<byte[]> GetUrlContentsAsync(string url)
11         {
12             //假設下載速度平均延遲 300 毫秒
13             await Task.Delay(300);
14 
15             using (var ms = new MemoryStream())
16             {
17                 var req = WebRequest.Create(url);
18 
19                 //var response = req.GetResponse();
20                 //Task<WebResponse> responseTask = req.GetResponseAsync();
21                 //var response = await responseTask;
22 
23                 using (var response = await req.GetResponseAsync())
24                 {
25                     //從指定 url 里讀取數據
26                     using (var rs = response.GetResponseStream())
27                     {
28                         //從當前流中讀取位元組並將其寫入到另一流中
29                         //rs.CopyTo(ms);
30                         await rs.CopyToAsync(ms);
31                     }
32                 }
33 
34                 return ms.ToArray();
35             }
36         }
GetUrlContentsAsync 方法

 

  2.仿造上述過程將 SumSizes 方法 => SumSizesAsync 非同步方法。

 1         /// <summary>
 2         /// 非同步統計總數
 3         /// </summary>
 4         private async Task SumSizesAsync()
 5         {
 6             //載入網址
 7             var urls = InitUrlInfoes();
 8 
 9             //位元組總數
10             var totalCount = 0;
11             foreach (var url in urls)
12             {
13                 //返回一個 url 內容的位元組數組
14                 var contents = await GetUrlContentsAsync(url);
15 
16                 //顯示結果
17                 DisplayResults(url, contents);
18 
19                 //更新總數
20                 totalCount += contents.Length;
21             }
22 
23             tbResult.Text += $"\r\n         Total: {totalCount}, OK!";
24         }

 

  3.再修改下 btnSwitch_Click

  這裡為防止意外地重新輸入操作,先在頂部禁用按鈕,在最終完成時再啟用按鈕。通常,不更改事件處理程式的名稱。 因為事件處理程式不需要返回值,所以返回類型也不需要更改為 Task。

 1         /// <summary>
 2         /// 非同步點擊事件
 3         /// </summary>
 4         /// <param name="sender"></param>
 5         /// <param name="e"></param>
 6         private async void btnSwitch_Click(object sender, RoutedEventArgs e)
 7         {
 8             btnSwitch.IsEnabled = false;
 9 
10             //清除文本框所有內容
11             tbResult.Clear();
12 
13             //統計總數
14             await SumSizesAsync();
15 
16             btnSwitch.IsEnabled = true;
17         }

 

  4.其實可以採用 .NET 自帶的 GetByteArrayAsync 非同步方法替換我們自己寫的 GetUrlContentsAsync 非同步方法,之前只是為了演示的需要。

  var hc = new HttpClient() { MaxResponseContentBufferSize = 1024000 };

  //var contents = await GetUrlContentsAsync(url);  
  var contents = await hc.GetByteArrayAsync(url);
 1         /// <summary>
 2         /// 非同步統計總數
 3         /// </summary>
 4         private async Task SumSizesAsync()
 5         {
 6 
 7             var hc = new HttpClient() { MaxResponseContentBufferSize = 102400 };
 8             //載入網址
 9             var urls = InitUrlInfoes();
10 
11             //位元組總數
12             var totalCount = 0;
13             foreach (var url in urls)
14             {
15                 //返回一個 url 內容的位元組數組
16                 //var contents = await GetUrlContentsAsync(url);
17                 var contents = await hc.GetByteArrayAsync(url);
18 
19                 //顯示結果
20                 DisplayResults(url, contents);
21 
22                 //更新總數
23                 totalCount += contents.Length;
24             }
25 
26             tbResult.Text += $"\r\n         Total: {totalCount}, OK!";
27         }
修改後的:SumSizesAsync 方法

   這時,項目的變換從同步到非同步操作已經完成。

 

 

  修改後的效果差異:最重要的是,UI 線程不會阻塞下載過程。當 web 資源下載、統計並顯示時,可以移動或調整視窗的大小。如果其中一個網站速度或不響應,可以通過選擇關閉按鈕取消了操作 (右上角的 X)。

      Demo 下載

 

同系列的隨筆

 


【參考引用】微軟官方文檔圖片


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

-Advertisement-
Play Games
更多相關文章
  • 新的2016MBP終於發佈了,作為把蘋果電腦裝WIN使用的人,等候很久之後,終於可以行動了。 1、之前的2013款Macbook Air 2013年之前一直用的是DELL電腦,由於DELL鍵盤左下角的Fn鍵在Ctrl鍵的右側,已經慣了多年,所以一直對於ThinkPad和Apple這樣Fn和Ctrl鍵... ...
  • spi子系統之驅動SSD1306 OLED 接觸Linux之前,曾以為讀源碼可以更快的學習軟體,於是前幾個博客都是一邊讀源碼一邊添加註釋,甚至精讀到每一行代碼,實際上效果並不理想,看過之後就忘記了。主要原因是沒理解透程式架構,各個模塊之間的關係,如何聯繫在一起,再加上沒有實例驗證。後來逐漸發現,理解 ...
  • 情況描述: 當安裝某些軟體後,磁碟根目錄中多出了msdia80.dll文件,該文件顯示為2006年12月1日,884KB。 原因: 當使用64位操作系統時,在電腦上安裝 Microsoft Visual C++ 2005 可再發行組件時, Msdia80.dll文件被安裝在啟動驅動器的根文件夾中。 ...
  • 【大型網站技術實踐】初級篇:藉助Nginx搭建反向代理伺服器(轉) 出處:http://edisonchou.cnblogs.com/ 一、反向代理:Web伺服器的“經紀人” 1.1 反向代理初印象 反向代理(Reverse Proxy)方式是指以代理伺服器來接受internet上的連接請求,然後將 ...
  • seL4之hello 2旅途 2016/11/19 13:15:38 If you like my blog, please buy me a cup of coffee. 回顧上周 seL4運行環境搭建. 完成 的運行. 補充上周 1.微內核與巨集內核有什麼不同? 微內核——用戶態提供OS服務,一般 ...
  • 在開發中,由於某些需求,我們可能需要做一些平移,縮放,旋轉甚至三維變換,所以我來講講在UWP中這些變換的實現方法。 一、 二維變換: 二維變換用到的是RenderTransform a、TranslateTransform,平移: 屬性:X,Y我相信大家都知道怎麼用,這裡就不講廢話了 b、Rotat ...
  • 項目相關的快捷鍵 Ctrl + Shift + B = 生成項目 Ctrl + Alt + L = 顯示Solution Explorer(解決方案資源管理器) Shift + Alt+ C = 添加新類 Shift + Alt + A = 添加新項目到項目 編輯相關的鍵盤快捷鍵 Ctrl + En... ...
  • 問題 Web API 怎麼支持通用的 OData 系統查詢項,例如 $select 或 $filter。 解決方案 為了在 Web API 中啟用查詢項,我們需要在 Action 上使用 EnableQueryAttribute。 如果 Action 沒有返回集合,而是返回單個對象的實例,調用端仍然 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...