[.NET] 怎样使用 async & await 一步步将同步代码转换为异步编程(整理中...)_.NET_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > .NET > [.NET] 怎样使用 async & await 一步步将同步代码转换为异步编程(整理中...)

[.NET] 怎样使用 async & await 一步步将同步代码转换为异步编程(整理中...)

 2016/11/21 5:30:57  反骨仔(二五仔)  程序员俱乐部  我要评论(0)
  • 摘要:怎样使用async&await一步步将同步代码转换为异步编程【博主】反骨仔【出处】http://www.cnblogs.com/liqingwen/p/6079707.html序上次,博主通过《利用async&await的异步编程》一文介绍了async&await的基本用法及异步的控制流和一些其它的东西。今天,博主打算从创建一个普通的WPF应用程序开始,看看如何将它逐步转换成一个异步的解决方案
  • 标签:.net 使用 net 代码 编程 同步 异步

class="sentence" data-guid="164c6def0a3ef07e80a4fc2b2977b7de" data-source="You can write asynchronous programs more easily and intuitively by using features that are introduced in Visual Studio 2012.">怎样使用 async & await 一步步将同步代码转换为异步编程

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

  上次,博主通过《利用 async & await 的异步编程》一文介绍了 async & await 的基本用法及异步的控制流和一些其它的东西。

  今天,博主打算从创建一个普通的 WPF 应用程序开始,看看如何将它逐步转换成一个异步的解决方案

Foundation (WPF) application that sums the number of bytes in a list of websites."> 

目录

  • 介绍
  • 添加引用
  • 先创建一个同步的 WPF
  • 将上面的 demo 逐步转换为异步方法

 

介绍

  这里通过一个普通的 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

 

测试结果图

   全部显示需要耗费数秒的时间。在点击的thread is blocked while it waits for requested resources to download.">同时,它在等待请求资源下载时,UI 线程阻塞。因此,在点击“启动”按钮后,将无法移动,最大化,最小化,甚至关闭显示窗口。直到结果显示之前这些操作都会没有效果。failed.">如果网站没有响应时,您没有站点失败的表示形式。即使不想再继续等待并关闭程序都会很困难。

 

将上面的 demo 逐步转换为异步方法

  1.GetUrlContents 方法 => GetUrlContentsAsync 异步方法

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

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

  

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

  GetResponseAsync 将返回 Task。 has type <span class="mtpsTagOuterHtml" >WebResponse.">在这种情况下,任务返回变量 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 adb078" data-source="The call to <span class="code">webReq.GetResponseAsync returns a <span class="input">Task(Of WebResponse) or <span class="input">Task&lt;WebResponse&gt;.">为 webReq.GetResponseAsync 的调用返回 Task 或 Task<WebResponse>。 然后 await 运算符应用于 task 检索 WebResponse 值。

  

  (3)由于在上一步中添加了 await 运算符,编译器会报告错误。await 运算符在标有 async 的方法下才能使用。当您重复转换步骤替换 CopyTo 为 CopyToAsync 时,请先暂时忽略该错误。

  • 更改调用 CopyToAsync方法的名称。

  • CopyTo 或 CopyToAsync 方法复制字节为其参数,不返回有意义的值。 在同步版本中,CopyTo 的调用不返回值。在异步版本中,即CopyToAsync,返回 Task,可functions like "Task(void)" and enables the method to be awaited.">应用 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 修饰符。添加修饰符标记方法作为异步方法。

  //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 资源下载、计数并显示时,可以移动或调整窗口的大小。operation by choosing the <span class="label">Close button (the x in the red field in the upper-right corner).">如果其中一个网站速度或不响应,可以通过选择关闭按钮取消了操作 (右上角的 X)。

 

同系列的随笔

  • 利用 async & await 的异步编程
  • 走进异步编程的世界 - 开始接触 async/await
  • 走进异步编程的世界 - 剖析异步方法(上)
  • 走进异步编程的世界 - 剖析异步方法(下)
  • 走进异步编程的世界 - 在 GUI 中执行异步操作

 


【参考引用】微软官方文档图片

发表评论
用户名: 匿名