[.NET] 怎样使用 async await 一步步将同步代码转换为异步编程
怎样使用 async & await 一步步将同步代码转换为异步编程
【博主】反骨仔 【出处】http://www.cnblogs.com/liqingwen/p/6079707.html
序
上次,博主通过《利用 async & await 的异步编程》该篇点睛之作介绍了 async & await 的基本用法及异步的控制流和一些其它的东西。
今天,博主打算从创建一个普通的 WPF 应用程序开始,看看如何将它逐步转换成一个异步的解决方案。
目录
- 介绍
- 添加引用
- 先创建一个同步的 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
测试结果图
界面上的内容显示需要花费一定的时间(可能是数秒)。
当你点击启动的同时,也就是下载 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); 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 下载
同系列的随笔
- 利用 async & await 的异步编程
- 走进异步编程的世界 - 开始接触 async/await
- 走进异步编程的世界 - 剖析异步方法(上)
- 走进异步编程的世界 - 剖析异步方法(下)
- 走进异步编程的世界 - 在 GUI 中执行异步操作
【参考】https://docs.microsoft.com/en-us/dotnet/articles/csharp/programming-guide/concepts/async/walkthrough-accessing-the-web-by-using-async-and-await
【参考引用】微软官方文档
[.NET] 怎样使用 async await 一步步将同步代码转换为异步编程相关推荐
- 异步编程中的最佳做法(Async/Await) --转
近日来,涌现了许多关于 Microsoft .NET Framework 4.5 中新增了对 async 和 await 支持的信息. 本文旨在作为学习异步编程的"第二步":我假设 ...
- 【JS】1015- 异步编程的终极解决方案 async/await
早期的回调函数 回调函数我们经常有写到,比如: ajax(url, (res) => {console.log(res); }) 复制代码 但是这种回调函数有一个大缺陷,就是会写出 回调地狱(C ...
- 明明有了 promise ,为啥还需要 async await ?
作者 | Angus安格斯 来源 | https://juejin.cn/post/6960855679208783903 为了让还没听说过这个特性的小伙伴们有一个大致了解,以下是一些关于该特性的简要 ...
- Async/Await替代Promise的6个理由
2019独角兽企业重金招聘Python工程师标准>>> 译者按: Node.js的异步编程方式有效提高了应用性能:然而回调地狱却让人望而生畏,Promise让我们告别回调函数,写出更 ...
- 【转】2.2[译]async/await中阻塞死锁
这篇博文主要是讲解在async/await中使用阻塞式代码导致死锁的问题,以及如何避免出现这种死锁.内容主要是从作者Stephen Cleary的两篇博文中翻译过来. 原文1:Don'tBlock o ...
- Python 异步 IO 、协程、asyncio、async/await、aiohttp
From :廖雪峰 异步IO :https://www.liaoxuefeng.com/wiki/1016959663602400/1017959540289152 Python Async/Awai ...
- 【ES8(2017)】async / await
async 和 await 是一种更加优雅的异步编程解决方案,是Promise 的拓展. 在我们处理异步的时候,比起回调函数,Promise的then方法会显得较为简洁和清晰,但是在处理多个彼此之间相 ...
- Python3 中的 asyncio async await 概念(实例)(ValueError: too many file descriptors in select())
代码实例 import timedef demo4():"""这是最终我们想要的实现."""import asyncio # 引入 asyn ...
- [译]JavaScript async / await:好处、坑和正确用法
原文地址:JavaScript async/await: The Good Part, Pitfalls and How to Use ES7通过介绍async/await使得JavaScript的异 ...
最新文章
- 【微信小程序企业级开发教程】界面刷新获取新更新数据
- DOS BAT用法简例子
- 常见的排序算法(1)
- vue-axios下载文件流blob,ie下载报传递给系统调用的数据区域太小.ie文件流下载报错;文件下载失败将blob的错误信息转换成json格式
- leetcode7 整数反转
- 40_并发编程-事件
- IDEA常用的风格设置
- TypeError: 'float' object is not iterable
- 杭电OJ 1720 进制处理
- 黑科技知识:需要登录才能访问的网站如何破解?仅仅只需 3 步!
- AWS CLI 安装配置
- 斐波那契生兔子问题(一月大兔子生a对,二月大兔子生b对,三月大兔子生c对。。。)
- 可信、安全、稳定构建金融科技新局面
- 在线教育录播视频防下载安全测试 _EduSoho_HLS(m3u8)
- 「湖南周边游」安化云台山风景区——适合亲子游的地方
- 穿越火线枪战王者服务器维护,CF手游8.24体验服维护公告 全新HD地图登场
- mybatis使用foreach
- react-native-waterfall-flow 高性能瀑布流组件
- 用css实现一个太极阴阳图,使用多个div块实现
- html5 data src显示,srcdoc =“…”和src =“data:text/html,…”之间的区别是什么?
热门文章
- 计算机音乐深海少女,深海少女 MIDI File Download :: MidiShow
- FireFox nsSessionStore.js 问题报错解决
- POJ - 3461 (kmp)
- 同步类容器与并发类容器
- Minio 报错bucket name does not follow Amazon S3 standards
- java button随机颜色_Javascript点击按钮随机改变数字与其颜色
- 虚拟机环境下ansible方式部署tidb3.0时系统检测不通过
- 数据库性能自动压测-Oracle swingbench篇
- screw ---- 数据库转文档
- ftp ---- 文件结构(配置简单整理)