【本文转自:http://www.cnblogs.com/x-xk/archive/2013/06/05/3118005.html  作者:

好久没写博客了,时隔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介绍了三种可能的返回类型:TaskTask<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线程是唯一的一个可以控制一个特定窗口的线程,也是唯一的线程能检测用户的操作,并对它们做出响应。

  这次介绍就到这了。

async 与 await异步编程活用基础的更多相关文章

  1. async And await异步编程活用基础

    原文:async And await异步编程活用基础 好久没写博客了,时隔5个月,奉上一篇精心准备的文章,希望大家能有所收获,对async 和 await 的理解有更深一层的理解. async 和 a ...

  2. Async和Await异步编程的原理

    1. 简介 从4.0版本开始.NET引入并行编程库,用户能够通过这个库快捷的开发并行计算和并行任务处理的程序.在4.5版本中.NET又引入了Async和Await两个新的关键字,在语言层面对并行编程给 ...

  3. .net4.5使用async和await异步编程实例

    关于异步编程的简单理解: 在.NET4.5中新增了异步编程的新特性async和await,使得异步编程更为简单.通过特性可以将这项复杂的工作交给编译器来完成了.之前传统的方式来实现异步编程较为复杂,这 ...

  4. Asycn/Await 异步编程初窥

    经过两天密集型的学习,翻阅了大量 webpages ,点击了不少重点 blogs,总算基本了解了一些 async/await 搭配使用的入门技巧,总结一下 1. async/await 应该只是语法上 ...

  5. 【转】C# Async/Await 异步编程中的最佳做法

    Async/Await 异步编程中的最佳做法 Stephen Cleary 近日来,涌现了许多关于 Microsoft .NET Framework 4.5 中新增了对 async 和 await 支 ...

  6. C#中 Thread,Task,Async/Await 异步编程

    什么是异步 同步和异步主要用于修饰方法.当一个方法被调用时,调用者需要等待该方法执行完毕并返回才能继续执行,我们称这个方法是同步方法:当一个方法被调用时立即返回,并获取一个线程执行该方法内部的业务,调 ...

  7. .NET Web应用中为什么要使用async/await异步编程

    前言 什么是async/await? await和async是.NET Framework4.5框架.C#5.0语法里面出现的技术,目的是用于简化异步编程模型. async和await的关系? asy ...

  8. Asycn/Await 异步编程

    Asycn/Await 异步编程初窥(二)   经过总过4天的学习和实践,做完了 WinForm 下 .Net 4.5 的基本异步应用,实现了一个 Http 协议下载的测试程序,为以后使用 .Net ...

  9. [.NET 4.5] ADO.NET / ASP.NET 使用 Async 和 Await 异步 存取数据库

    此为文章备份,原文出处(我的网站)  [.NET 4.5] ADO.NET / ASP.NET 使用 Async 和 Await 异步 存取数据库 http://www.dotblogs.com.tw ...

随机推荐

  1. linux下配置NFS服务器

    (声明:本文大部分文字摘自Linux NFS服务器的安装与配置) 一.NFS简介     NFS 是Network File System的缩写,即网络文件系统.一种使用于分散式文件系统的协定,由Su ...

  2. Test for open live write

    this is test document. this is test document. this is test document. this is test document. this is ...

  3. Uva_11916 Emoogle Grid

    题目链接 题意: 有个N X M的棋盘, 有K种颜色, 有B个不可涂色的位置, 共有R种涂色方案. 1)每个可涂色的位置必须涂上一种颜色 2)不可涂色位置不能涂色 3)每个位置必须从K种颜色中选出一种 ...

  4. importExcel运用注解实现EXCEL导入poi类

    JAVA报表 package com.app.common.excel; import java.io.File; import java.io.FileInputStream; import jav ...

  5. 李洪强漫谈iOS开发[C语言-033]-三元运算符的应用

  6. CyclicBarrier的介绍和使用

    转自:http://www.itzhai.com/the-introduction-and-use-of-cyclicbarrier.html 类说明: 一个同步辅助类,它允许一组线程互相等待,直到到 ...

  7. Git命令详解

    一个中文git手册:http://progit.org/book/zh/ 原文:http://blog.csdn.net/sunboy_2050/article/details/7529841 前面两 ...

  8. Error: opening registry key 'Software\JavaSoft\Java Runtime Environment' Error: could not find java.dll

    java -jar yxCollector-1.1.0.jarError: opening registry key 'Software\JavaSoft\Java Runtime Environme ...

  9. 余弦距离、欧氏距离和杰卡德相似性度量的对比分析 by ChaoSimple

      1.余弦距离 余弦距离,也称为余弦相似度,是用向量空间中两个向量夹角的余弦值作为衡量两个个体间差异的大小的度量. 向量,是多维空间中有方向的线段,如果两个向量的方向一致,即夹角接近零,那么这两个向 ...

  10. KiCad中层定义

    5.2.1. Paired layers The Adhesives layers (Copper and Component):    These are used in the applicati ...