- 浏览: 86627 次
-
文章分类
- 全部博客 (136)
- 我的技术资料收集 (98)
- 具体技术 (1)
- 的技术资料收集 (4)
- All Articles (1)
- 机器学习 Machine Learning (1)
- 网络编程 (1)
- java (2)
- ava (1)
- 零散技术 (1)
- C# (3)
- 技术资料收集 (1)
- CQRS (1)
- 数据库技术(MS SQL) (1)
- .Net微观世界 (1)
- Oracle SQL学习之路 (1)
- C/C++ (1)
- JS/JQ (1)
- Js封装的插件/实例/方法 (2)
- 敏捷个人 (2)
- Javascript (1)
- 程序设计---设计模式 (1)
- Bug (1)
- 未知分类 (1)
- 程序设计 (1)
- Sharepoint (1)
- Computer Graphic (1)
- IT产品 (1)
- [06]JS/jQuery (1)
- [07]Web开发 (1)
- .NET Solution (1)
- Android (3)
- 机器学习 (1)
- 系统框架设计 (1)
- Others (1)
- 算法 (1)
- 基于Oracle Logminer数据同步 (1)
- 网页设计 (1)
- 原创翻译 (1)
- EXTJS (1)
- Jqgrid (1)
- 云计算 (1)
最新评论
好久没写博客了,时隔5个月,奉上一篇精心准备的文章,希望大家能有所收获,对async 和 await 的理解有更深一层的理解。
async 和 await 有你不知道的秘密,微软会告诉你吗?
我用我自己的例子,去一步步诠释这个技术,看下去,你绝对会有收获。(渐进描述方式,愿适应所有层次的程序员)
从零开始, 控制台 Hello World:
什么?开玩笑吧?拿异步做Hello World??
下面这个例子,输出什么?猜猜?
1 static void Main(string[] args)
2 {
3
4 Task t = example1();
5 }
6
7 static async Task DoWork()
8 {
9
10 Console.WriteLine("Hello World!");
11 for (int i = 0; i < 3; i++)
12 {
13 Console.WriteLine("Working..{0}",i);
14 await Task.Delay(1000);//以前我们用Thread.Sleep(1000),这是它的替代方式。
15 }
16 }
17 static async Task example1()
18 {
19 await DoWork();
20 Console.WriteLine("First async Run End");
21 }
先不要看结果,来了解了解关键字吧,你确定你对async 和await了解?
async 其实就是一个标记,标记这个方法是异步方法。
当方法被标记为一个异步方法时,那么其方法中必须要使用await关键字。
重点在await,看字面意思是等待,等待方法执行完成。
它相当复杂,所以要细细讲述:
当编译器看到await关键字后,其后的方法都会被转移到一个单独的方法中去独立运行。独立运行?
是不是启动了另一个线程?
嗯。有这个想法的同学,很不错。就是这个答案。
我们来看看执行顺序。来验证一下我的这个说法,加深大家对await的理解。
首先从入口example1 进入:
——>碰见await Dowork()
——>此时主线程返回
——>进入DoWork
——>输出“Hello World!”
——>碰见await 关键字
——>独立执行线程返回
——>运行结束
我们看到3个输出语句,按照我的说法,最终会出几个?猜猜,动手验证答案,往往是最实在的,大部分程序都不会骗我们。如果对Task有不熟悉的,可以参看本人博客先前写Task的部分
我们现在看到的就是,程序进入一个又一个方法后,输出个Hello World 就没了,没有结束,没有跳出。因为是异步,所以我们看不到后续的程序运行了。
我为什么要用控制台来演示这个程序?
这个疑问,让我做了下面的例子测试,一个深层次的问题,看不懂跳过没有丝毫影响。
分析一下这个例子:
1 static void Main(string[] args)
2 {
3 example2();
4 }
5
6 static async void example2()
7 {
8 await DoWork();
9 Console.WriteLine("First async Run End");
10 }
11
12 static async Task DoWork()
13 {
14 Console.WriteLine("Hello World!");
15 for (int i = 0; i < 3; i++)
16 {
17 await Task.Delay(1000);
18 Console.WriteLine("Working..{0}",i);
19 }
20 }
运行丝毫问题,结果依旧是“Hello World ”,似乎更简单了。
注意,细节来了,example2 是void,Mani也是void,这个相同点,似乎让我们可以这么做:
1 static async void Main(string[] args)//给main加个 async
2 {
3 await DoWork();
4 }
5 static async Task DoWork()
6 {
7 Console.WriteLine("Hello World!");
8 for (int i = 0; i < 3; i++)
9 {
10 await Task.Delay(1000);
11 Console.WriteLine("Working..{0}",i);
12 }
13 }
程序写出,编译器没有错误,运行->
一个异步方法调用后将返回到它之前,它必须是完整的,并且线程依旧是活着的。
而main正因为是控制台程序的入口,是主要的返回操作系统线程,所以编译器会提示入口点不能用async。
下面这种事件,我想大家不会陌生吧?WPF似乎都用这种异步事件写法:
1 private async void button1_Click(object sender, EventArgs e)
2 {
3
4 //….
5
6 }
以此列Main入口,类推在ASP.NET 的 Page_Load上也不要加async,因为异步Load事件内的其他异步都会一起执行,死锁? 还有比这更烦人的事吗?winfrom WPF的Load事件目前没有测试过,现在的事件都有异步async了,胡乱用,错了你都不知道找谁。
好小细节提点到了,这个牵出的问题也就解决了。
有心急的同学可能就纳闷了,第一个例子,怎么才能看到先前的输出啊?
别急加上这句:
1 static void Main(string[] args)
2 {
3 Task t = example1();
4
5 t.Wait();//add
6 }
输出窗口就可以看到屏幕跳动连续输出了、、、
入门示例已经介绍完了,来细细品味一下下面的知识吧。
Async使用基础总结
到此Async介绍了三种可能的返回类型:Task,Task<T>和void。
但是async方法的固有返回类型只有Task和Task<T>,所以尽量避免使用async void。
并不是说它没用,存在即有用,async void用于支持异步事件处理程序,什么意思?(比如我例子里面那些无聊的输出呀..)或者就如上述提到的:
1 private async void button1_Click(object sender, EventArgs e)
2
3 {
4
5 //….
6
7 }
有兴趣的同学可以去找找(async void怎么支持异步事件处理程序)
异常处理介绍
async void 的方法具有不同的错误处理语义,因为在Task和Task<T>方法引发异常时,会捕获异常并将其置于Task对象上,方便我们查看错误信息,而async void,没有Task对象,没有对象直接导致异常都会直接在SynchronizationContext上引发(SynchronizationContext是async 和 await的实现底层哦)既然提到了SynchronizationContext,那么我在这说一句:
SynchronizationContext 对任何编程人员来说都是有益的。
无论是什么平台(ASP.NET、Windows 窗体、Windows Presentation Foundation (WPF)、Silverlight 或其他),所有 .NET 程序都包含 SynchronizationContext 概念。(建议好学的同学去找找)
扯远了,回到之前谈到的 async void 和 Task 异常,看看两种异常的结果,看看测试用例。
首先是 async void:
1 static void Main(string[] args)
2 {
3 AsyncVoidException();
4 }
5 static async void ThrowExceptionAsync()
6 {
7 throw new OutOfMemoryException();
8 }
9 static void AsyncVoidException()
10 {
11 try
12 {
13 ThrowExceptionAsync();
14 }
15 catch (Exception)
16 {
17
18 throw;
19 }
20 }
没有丝毫异常抛出,我先前说了,它会直接在SynchronizationContext抛出,但是执行异步的时候,它丝毫不管有没有异常,执行线程直接返回,异常直接被吞,所以根本无法捕获async void 的异常。我就不上图了,偷懒了。。
再看看async Task测试用例:
1 static void Main(string[] args)
2 {
3 AsyncVoidException();
4 }
5 static async Task ThrowExceptionAsync()
6 {
7 await Task.Delay(1000);
8 throw new OutOfMemoryException();
9 }
10 static void AsyncVoidException()
11 {
12 try
13 {
14 Task t = ThrowExceptionAsync();
15 t.Wait();
16 }
17 catch (Exception)
18 {
19
20 throw;
21 }
22 }
预料之中啊:
通过比较,大家不难看出哪个实用哪个不实用。
对于async void 我还要闲扯一些缺点,让大家认识到,用这个的确要有扎实的根底。
很显然async void 这个方法未提供一种简单的方式,去通知向调用它的代码发出回馈信息,通知是否已经执行完成。
启动async void方法不难,但你要确定它何时结束也是不易。
async void 方法会在启动和结束时去通知SynchronizationContext。简单的说,要测试async void 不是件简单的事,但有心去了解,SynchronizationContext或许就不这么难了,它完全可以用来检测async void 的异常。
说了这么多缺点,该突出些重点了:
建议多使用async Task 而不是async void。
async Task方法便于实现错误处理、可组合性和可测试性。
不过对于异步事件处理程序不行,这类处理程序必须返回void。
异步——我们既陌生又熟悉的朋友——死锁!
对于异步编程不了解的程序员,或许常干这种事:
混合使用同步和异步代码,他们仅仅转换一小部分应用程序,提出一段代码块,然后用同步API包装它,这么做方便隔离,同步分为一块,异步分为另一块,这么做的后果是,他们常常会遇到和死锁有关的问题。
我之前一直用控制台来写异步,大家应该觉得,异步Task就是这么用的吧?没有丝毫阻噻,都是理所当然的按计划运行和结束。
嗯,来个简单的例子,看看吧:
这是我的WPF项目的测试例子:
1 int i = 0;
2 private void button_1_Click(object sender, RoutedEventArgs e)
3 {
4
5 textBox.Text += "你点击了按钮 "+i++.ToString()+"\t\n";
6 Task t = DelayAsync();
7 t.Wait();
8 }
9 private static async Task DelayAsync()
10 {
11
12 MessageBox.Show("异步完成");
13 await Task.Delay(1000);
14 }
为了便于比较,看看控制台对应的代码:
1 static void Main(string[] args)
2 {
3 Task t = DelayAsync();
4 t.Wait();
5 }
6 private static async Task DelayAsync()
7 {
8 await Task.Delay(1000);
9 Console.WriteLine("Complet");
10 }
控制台程序没有丝毫问题,我保证。
现在来注意一下WPF代码,当我button点击之后,应该出现的效果是:
看图片的效果不错。
接着你关掉提示框,你会发现 ,这个窗口点什么都没用了。关闭的不行,我确定我说的没错。
想关掉 就去任务管理器里面结束进程吧~~~
这是一个很简单的死锁示例,我想说的是差不多的代码,在不同的应用程序里面会有不一样的效果,这就是它灵活和复杂的地方。
这种死锁的根本原因是await处理上下文的方式。
默认情况下,等待未完成的Task时,会捕获当前“上下文”,在Task完成时使用该上下文回复方法的执行(这里的“上下文”指的是当前TaskScheduler任务调度器)
值得注意的就是下面这几句代码:
1 t.Wait();
2
3 private static async Task DelayAsync()
4 {
5
6 MessageBox.Show("异步完成");
7 await Task.Delay(1000);
8 }
请确定你记住他的结构了,现在我来细讲原理。
Task t 有一个线程块在等待着 DelayAsync 的执行完成。
而 async Task DelayAsunc 在另一个线程块中执行。
也就是说,在 MessageBox.Show("异步完成"); 这个方法完成后,await 会继续获取 async 余下的部分,它还能捕获到接下来的代码吗?
async的线程已经被t线程在等待了,t在等待 async的完成,而运行Task.Delay(1000)后,await就会尝试在捕获的上下文中执行async方法的剩余部分,async被占用了,它就在等待t。然后它们就相互等待对方,从而导致死锁,锁上就不听使唤了~~~用个图来形容一下这个场景
说重点了。
为什么控制带应用程序不会形成这种死锁?
它们具有线程池SynchronizationContext(同步上下文),而不是每次执行一个线程块区的SynchronizationContext,以此当await完成时,它会在线程池上安排async方法的剩余部分。所以各位,在控制台写好的异步程序,移动到别的应用程序中就可能会发生死锁。
好,现在来解决这个WPF的异步错误,我想这应该会引起大家兴趣,解决问题是程序员最喜欢的活。
改Wait()为ConfigureAwait(false)像这样:
1 Task t = DelayAsync();
2
3 t.ConfigureAwait(continueOnCapturedContext:false);//这个写法复杂了点,但从可读性角度来说是不错的,你这么写t.ConfigureAwait(false)当然也没问题
什么是ConfigureAwait?
官方解释:试图继续回夺取的原始上下文,则为 true,否则为 false。
不好理解,我来详细解释下,这个方法是很有用的,它可以实现少量并行性:
使得某些异步代码可以并行运行,而不是一个个去执行,进行零碎的线程块工作,提高性能。
另一方面才是重点,它可以避免死锁。
Wait造成的相互等待,在用这个方法的时候,就能顺利完成,如意料之中自然。当然还有指导意见要说的,如果在方法中的某处使用ConfigureAwait,则建议对该方法中,此后每个await都使用它。
说到这,只怕有些同学觉得,能避免死锁,这么好!以后就用ConfigureAwait就行了,不用什么await了。
没有一种指导方式是让程序员盲目使用的,ConfigureAwait这个方法,在需要上下文的代码中是用不了的。看不懂?没关系,接着看。
await运行的是一种原始上下文,就比如这样:
1 static async Task example1()
2 {
3 await DoWork();
4 Console.WriteLine("First async Run End");
5 }
一个async对应一个await ,它们本身是一个整体,我们称它为原始上下文。
ConfigureAwait而它有可能就不是原始上下文,因为它的作用是试图夺回原始上下文。用的时候VS2012会帮我们自动标识出来:
出这个问题是我在事件前加了一个async声明。
添加异步标识后,ConfigureAwait就不能夺取原始上下文了,在这种情况下,事件处理程序是不能放弃原始上下文。
大家要知道的是:
每个async方法都有自己的上下文,如果一个async方法去调用另一个async方法,则其上下文是相互独立的。
为什么这么说?独立是什么意思?我拿个例子说明吧:
1 private async void button_1_Click(object sender, RoutedEventArgs e)
2 {
3 Task t = DelayAsunc();
4
5 t.ConfigureAwait(false);//Error
6
7 }
8 private static async Task DelayAsunc()
9 {
10 MessageBox.Show("异步完成");
11 await Task.Delay(1000);
12 }
因为是独立的,所以ConfigureAwait不能夺取原始上下文,错误就如上那个图。
修改一下:
1 private async void button_1_Click(object sender, RoutedEventArgs e)
2 {
3 Task t = DelayAsunc();
4
5 t.Wait();
6 }
7 private static async Task DelayAsunc()
8 {
9 MessageBox.Show("异步完成");
10 await Task.Delay(1000).ConfigureAwait(continueOnCapturedContext:false);
11 }
每个async 都有自己的上下文,因为独立所以它们之间的调用是可行的。
修改后的例子,将事件处理程序的所有核心逻辑都放在一个可测试且无上下文的async Task方法中,仅在上下文相关事件处理程序中保存最少量的代码。
至此,已经总结了3条异步编程指导原则,我一起集合一下这3条,方便查阅。
我们都忽略了一个问题,可能大家从来都没想过,
我们对代码操作,一直都是一种异步编程。而我们的代码都运行在一个操作系统线程!
来看些最简单的应用,帮助大家能快速的熟悉,并使用,才是我想要达到的目的,你可以不熟练,可以不会用,但是,你可以去主动接近它,适应它,熟悉它,直到完全活用。
异步编程是重要和有用的。
下面来做些基本功的普及。我先前提到UI线程,什么是UI线程?
我们都碰见过程序假死状态,冻结,无响应。
微软提供了UI框架,使得你可以使用C#操作所有UI线程,虽说是UI框架,我想大家都听过,它们包括:WinForms,WPF,Silverlight。
UI线程是唯一的一个可以控制一个特定窗口的线程,也是唯一的线程能检测用户的操作,并对它们做出响应。
这次介绍就到这了。
发表评论
-
C#WebBrowser控件使用教程与技巧收集--苏飞收集 - sufeinet
2013-06-28 12:07 1096原帖地址:http://www.cnblogs.com/suf ... -
我要喷一个自认为很垃圾的网站架构 - 老赵【苏州】
2013-06-28 12:01 1163原帖地址:http://www.cnblogs.com/lao ... -
[翻译] Oracle Database 12c 新特性Multitenant - Cheney Shue
2013-06-28 11:43 647原帖地址:http://www.cnblogs.com/ese ... -
memcahd 命令操作详解 - 阿正-WEB
2013-06-28 11:37 494原帖地址:http://www.cnblogs.com/azh ... -
面向过程的代码符合大众的思维方式吗? - 史蒂芬.王
2013-06-27 10:28 619原帖地址:http://www.cnblogs.com/ste ... -
面向过程的代码符合大众的思维方式吗? - 史蒂芬.王
2013-06-27 10:28 576原帖地址:http://www.cnblogs.com/ste ... -
RPG游戏之组队测试 - zthua
2013-06-27 10:22 574原帖地址:http://www.cnblogs.com/zth ... -
IT人们给个建议 - SOUTHER
2013-06-26 14:06 538原帖地址:http://www.cnblogs.com/sou ... -
Java向前引用容易出错的地方 - 银河使者
2013-06-26 14:00 513原帖地址:http://www.cnblogs.com/nok ... -
使用Func<T1, T2, TResult> 委托返回匿名对象 - 灰身
2013-06-26 13:54 824原帖地址:http://www.cnblo ... -
【web前端面试题整理03】来看一点CSS相关的吧 - 叶小钗
2013-06-25 10:45 818原帖地址:http://www.cnblogs.com/yex ... -
Windows 8 动手实验系列教程 实验6:设置和首选项 - zigzagPath
2013-06-25 10:27 640原帖地址:http://www.cnblogs.com/zig ... -
闲聊可穿戴设备 - shawn.xie
2013-06-25 10:21 590原帖地址:http://www.cnblo ... -
CentOS下Mysql安装教程 - 小学徒V
2013-06-23 15:24 628原帖地址:http://www.cnblogs.com/xia ... -
vmware安装ubuntu12.04嵌套安装xen server(实现嵌套虚拟化) - skyme
2013-06-23 15:18 857原帖地址:http://www.cnblogs.com/sky ... -
之前专门为IE6、7开发的网站如何迁移到IE10及可能遇到的问题和相应解决方案汇总 - 海之澜
2013-06-23 15:12 976原帖地址:http://www.cnblogs.com/wuz ... -
Android学习笔记--解析XML之SAX - 承香墨影
2013-06-23 15:01 427原帖地址:http://www.cnblo ... -
SQL Server 性能优化之——T-SQL TVF和标量函数
2013-06-19 09:32 702原帖地址:http://www.cnblogs.com/Boy ... -
Nginx学习笔记(二) Nginx--connection&request
2013-06-19 09:26 701原帖地址:http://www.cnblogs.com/cod ... -
从郭美美霸气侧漏看项目管理之项目经理防身术
2013-06-19 09:20 517原帖地址:http://www.cnblogs.com/had ...
相关推荐
Async 和 Await keywords 是 Swift 5.5 中引入的两个关键字,用于简化异步编程模型的编写。 在 Swift 5.5 中,Async 和 Await keywords 可以用于简化异步编程模型的编写,使代码更加简洁易读。Async keyword 用于...
标题中的“Async-and-Await”指的是JavaScript中的async函数和await关键字,它们使得异步编程更加同步化,降低了理解和调试的难度。async函数本质上返回一个Promise,而await关键字则用于等待Promise的结果。这种...
Rust的异步编程模型基于 futures、tasks 和 async/await 关键字,这使得编写高效、线程安全的并发代码成为可能。下面将详细探讨这些关键概念。 1. **Futures(未来)** Futures 是 Rust 异步编程的核心概念,代表...
JavaScript异步编程是...理解并掌握异步编程的基本概念、使用场景以及Promise和async/await的使用,对于任何JavaScript开发者来说都至关重要。通过合理运用这些工具,开发者能够构建出更高效、更健壮的前端应用。
1. **异步编程基础**: 异步编程的核心在于避免阻塞主线程,尤其是对于UI线程,它负责处理用户交互。.NET提供两种主要的异步模式:基于回调的异步编程(APM,如BeginInvoke/EndInvoke)和基于任务的异步编程(TAP,...
在.NET框架中,异步编程可以通过多种方式实现,比如使用async和await关键字。异步操作让UI能够保持响应,同时执行后台任务。 **WMI与异步编程的结合** 在文档中,提到了将WMI与异步模式结合来提升ListView控件的...
Async/Await简化了异步编程,使得代码更加直观且易于理解和调试。 4. **改进的调试和诊断工具(Improved Debugging and Diagnostics Tools)** - **IntelliTrace**: 提供了历史调试功能,可以回溯代码执行过程,...
JavaScript还提供了异步编程的能力,常见的有回调函数、Promise和async/await。异步编程在处理网络请求、定时任务和长时间运行的操作时非常有用,避免了程序因等待而阻塞。理解回调函数的原理和陷阱,以及如何使用...
11. **异步编程**:Promise、async/await等技术是现代JavaScript处理异步操作的标准方式,它们可以帮助解决回调地狱问题,让代码更加清晰可读。 12. **前端框架**:随着React、Vue、Angular等框架的流行,理解它们...
async/await语法糖使得异步编程更接近同步风格,提高了代码可读性。 八、ES6及后续版本新特性 从ES6开始,JavaScript引入了许多新特性,如箭头函数、模板字符串、解构赋值、let/const、模块系统(import/export)、...
在JavaScript中,由于其单线程特性,为了处理耗时操作(如读取文件或网络请求),我们通常会采用异步编程,而回调函数就是异步编程的基础。 二、回调函数的工作原理 1. 事件循环:JavaScript引擎通过事件循环机制...
7. **错误处理**:在异步编程中,错误处理同样重要。除了使用try...catch捕获同步错误,还要考虑使用Promise的catch()方法和async函数的try...catch处理异步错误。 8. **Throttling和Debouncing**:在处理频繁的...
JavaScript中的异步编程是编程领域中的一个重要概念,它使得开发者能够编写非阻塞式的代码,...随着技术的发展,现代JavaScript已经提供了更多高级的异步处理手段,如async/await,使异步编程变得更加简洁和易于理解。
JavaScript 还提供了其他异步编程方式,如 Promise、async/await 等。Promise 是一种处理异步操作的更优雅的方式,它将回调函数链式化,使代码结构更清晰。async/await 是基于 Promise 的语法糖,可以让异步代码看...
异步编程的核心在于`Task`类,它代表了一个异步操作,而`await`关键字用于等待任务完成,允许代码在等待期间继续执行其他任务。 此外,C# 5.0还引入了动态类型(`dynamic`),它允许在运行时确定变量的类型,增强了...
在JavaScript的世界里,异步编程一直是开发者们关注的重点。由于JavaScript是单线程的语言,它无法像其他语言那样进行阻塞式等待,因此通常采用回调函数来处理异步操作。然而,随着回调函数的增多,代码往往会出现...
高级编程部分会深入探讨更复杂的JavaScript特性,如闭包、原型链、异步编程(回调函数、Promise和async/await)以及模块化(CommonJS和ES6模块)。闭包是JavaScript中的一个重要特性,它允许函数访问并操作其外部...