600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > [.NET] 怎样使用 async await 一步步将同步代码转换为异步编程

[.NET] 怎样使用 async await 一步步将同步代码转换为异步编程

时间:2019-02-08 08:22:54

相关推荐

[.NET] 怎样使用 async  await 一步步将同步代码转换为异步编程

怎样使用 async & await 一步步将同步代码转换为异步编程

【博主】反骨仔【出处】/liqingwen/p/6079707.html

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

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

目录

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

介绍

这里通过一个普通的 WPF 程序进行讲解:

只是一个文本框和一个按钮,左边文本框的内容为点击右键按钮时所产生的结果。

添加引用

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

using System.IO;using ;using .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 "/",10 "/liqingwen/",11 "/liqingwen/p/5902587.html",12 "/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//从当前流中读取字节并将其写入到另一流中21rs.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

测试结果图

界面上的内容显示需要花费一定的时间(可能是数秒)。

当你点击启动的同时,也就是下载 url 内容的时候,即等待资源的一个过程,此时,UI 线程会进行阻塞。因为在等待资源的这一个期间,我们无法对 UI 进行其他操作,如:移动、最大、最小和关闭窗口等操作。这样会令用户非常反感,特别是时间一长的时候,就会出现界面尚未响应,并且下载时候(网站没有响应或响应时间过长),也无法凸显站点失败的有效信息。

在 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);30await 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 下载

同系列的随笔

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

【参考】/en-us/dotnet/articles/csharp/programming-guide/concepts/async/walkthrough-accessing-the-web-by-using-async-and-await

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

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。