.net 4.5如何使用Async和Await进行异步编程

通过使用异步编程,可避免出现性能瓶颈,并提高应用程序的整体响应。然而,技术编写异步应用程序的传统方法过于复杂,这使得异步程序难以编写,调试和维护。

Visual Studio2012引入了一个简单的开发方法,异步编程,我们可以充分利用.NET Framework 4.5 和 Windows Runtime中对异步的支持。这项复杂的工作将会交由编译器来搞定,开发人员就像是在使用同步代码来编写应用程序的逻辑结构,但其结果是,得到了所有异 步编程的优点,但只要付出一点点工作。

本主题简要介绍何时以及如何使用异步编程,其中包括支持说明本主题的例子。由此下载VS2012.

异步提高响应能力

异步性必不可少,因为现实中有很多潜在的,可阻塞应用程序响应的情况,如当你的应用程序访问网络,文件系统等等。比如访问Web资源,有时是缓慢的 或者是有延迟的。同步处理的话,如果有这样的一个阻塞产生,那么整个应用程序就必须等待。而在一个异步的过程中,应用程序可以先继续进行其他不依赖于网络 的工作,直到所有可能产生阻塞的任务完成后再处理这些任务。

下表显示了典型的可以通过异步编程提高响应的场景。列出的.NET Framework4.5和Windows Runtime的API包含支持异步编程的方法。

应用领域
支持异步方法的API

访问Web
HttpClient , SyndicationClient

使用文件
StorageFile, StreamWriter, StreamReader,XmlReader

使用图像
MediaCapture, BitmapEncoder, BitmapDecoder

WCF程序开发
Synchronous and Asynchronous Operations

使用sockets
Socket

异步性已经被证明对所有通过一个线程访问UI,或是处理UI相关的活动的应用都特别的有价值。如果在同步的应用程序中任何一个处理过程被阻塞,那就意味着所有的东西都被阻塞了。你的应用程将会停止响应,更糟糕的是你可能会得出这样的结论,这只是等待并不是失败。

当你使用异步方法,应用程序将继续响应的UI。您可以调整大小或最小化窗口,例如,当你不想等下去的时候,你可以关闭该应用程序。

现在这种基于异步的方法在你设计异步操作时,就像一组可以选择的自动变速器,也就是说,你可以得到所有之前异步编程的好处,而不必像之前那样苦逼(太让人兴奋了)。

异步方法现在很容易编写

Async和Await关键字是C#异步编程的核心。通过使用这两个关键字,你可以使用.NET Framework或Windows Runtime的资源创建一个异步方法如同你创建一个同步的方法一样容易。通过使用async和await定义的异步方法,这里被称为异步方法。

下面的例子显示了一个异步方法。代码中的几乎所有的东西你看起来应该非常熟悉。注释中描述了你为实现异步操作添加什么功能。

1 // 在签名中三个要注意的事项: 
2 //  - 该方法具有一个async修饰符.  
3 //  - 返回类型为 Task or Task<t>. (参考 "返回类型" 一节.)
4 //    这里, 返回值是 Task<int> 因为返回的是一个整数类型. 
5 //  - 这个方法的名称以 "Async" 结尾.
6 async Task<int> AccessTheWebAsync()
7 { 
8     // 你需要添加System.Net.Http的引用来声明client
9     HttpClient client = new HttpClient();
10 
11     // GetStringAsync 返回 Task<string>. 这意味着当Task结束等待之后 
12     // 你将得到一个string (urlContents).
13     Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");
14 
15     // 你可以做一些不依赖于 GetStringAsync 返回值的操作.
16     DoIndependentWork();
17 
18     // await 操作挂起了当前方法AccessTheWebAsync. 
19     //  - AccessTheWebAsync 直到getStringTask完成后才会继续. 
20     //  - 同时, 控制权将返回 AccessTheWebAsync 的调用者. 
21     //  - 控制权会在getStringTask完成后归还到AccessTheAsync.  
22     //  - await操作将取回getStringTask中返回的string结果. 
23     string urlContents = await getStringTask;
24 
25     // return语句用来指定一个整数结果。
26     // 调用AccessTheWebAsync将会收到一个返回值的长度. 
27     return urlContents.Length;
28 }

如果AccessTheWebAsync没有什么不依赖于GetStringAsync的内容,也可以直接调用如下代码:

string urlContents = await client.GetStringAsync();

以下几个特点总结了一下前面的例子中的异步方法。

方法中包含了 async 修饰符。

一个async方法按照惯例以“Async”结尾。

返回类型是如下类型之一:

Task<TResult> 当你的方法有返回值时,TResult即返回值的类型

Task 当你的方法没有return语句,或者返回值并不参与任何形式的运算(包括赋值操作)。

Void 当你编写一个异步事件处理时会用到

方法通常包括至少一个await的表达式,这意味着该方法在遇到await时不能继续执行,直到等待异步操作完成。在此期间,该方法将被暂停,并且控制权返回到该方法的调用者。本主题接下来的部分说明了悬挂点后会发生什么。

在异步方法中,我们使用这些已经提供的关键字和类型来表达想要做什么的时候,编译器并没有闲着,他将处理包括跟踪在暂停方法中控制权返回到 await点后将会如何处理。一些常规流程,如循环和异常处理,在之前的异步代码中都比较难以处理。而现在都归结到了一个async方法中,你会感觉你在 写一个同步的代码,之前的那些困惑已经不复存在了。

在异步方法中发生了什么

了解异步编程最重要的是理解控制权是如何在方法之间转移的。下面的图标将会解释这个过程。

.net 4.5如何使用Async和Await进行异步编程

图中的数字对应于下面的步骤。

1. 一个事件的处理函数用await的方式调用了异步方法 AccessTheWebAsync。

2. AccessTheWebAsync 创建了一个 HttpClient 实例,并调用了异步方法 GetStringAsync 来以下载一个页面并将内容以string的形式返回.

3. 在GetStringAsync中将会碰到一些让进程挂起的事情。也许它必须等待一个网站下载完成或其他一些阻塞性的动作,为了避免阻塞资源,GetStringAsync把控制权移交给了它的调用者AccessTheWEbAsync。

GetStringAsync返回Task<TResult>泛型,例子中的TResult是string类 型,AccessTheWebAsync将任务交给了getStringTask变量。这个任务代表了一个正在调用GetStringAsync的过程, 与一个承诺,当工作完成时,最终将产生string返回值。

4. 由于getStringTask并没有被(用await)等待着索取结果,so AccessTheWebAsync 可以继续其他不依赖于GetStringAsync最终返回结果的其他任务。这些任务由一个同步方法DoIndenpendentWork代表。

5.DoIndenpendentWork是一个同步方法,将会以同步的方式执行他的工作,并将返回值返回给调用者。

6. AccessTheWebAsync下一步是希望计算已经下载下来的字符长度,但是如果没有这个字符,这个希望也就破灭了。

因此,AccessTheWebAsync 使用了await关键字挂起了自己的线程,并将控制权移交到了AccessTheWebAsync的调用者。AccessTheWebAsync将会返回 一个Task<int>给调用函数。这个任务承诺会在结束是返回给调用者一个int型的返回值。

注意

如果GetStringAsync在AccessTheWebAsync 调用await之前就已经完成了,那么控制权将依然在AccessTheWebAsync中。这种挂起和等待的操作也是很消耗资源的,如果返回值在 await之前就已经得到了,AccessTheWebAsync没必要非得在用等待的方法去得到最终的结果。

在调用方法(这里是一个event的处理函数)的内部,处理过程是叠加的,event的处理函数将会等待AccessTheWebAsync,而 AccessTheWebAsync在等待GetStringAsync,与此同时,调用函数依然可以执行不依赖于这些返回值的操作。

7. GetStringAsync计算并产生一个string结果。这个string结果可能不是按照你现在期望的方式直接返回给他的调用函数,相反,这个结 果保存在一个代表方法完成的任务中,getStringTask。await操作符将会从getStringTask中取回期望的结果。赋值语句将会把结 果交给urlContents。

8. 当AccessTheWebAsync获得了这个字符串结果,我们可以继续计算这个字符串的长度了。这样AccessTheWebAsync的工作也完成了,等待中的event处理函数也可以继续了。

如果你刚刚接触异步编程,那应该花一分钟来思考一下同步行为和异步行为的不同。同步方法在工作完成之后返回(步骤5),但是异步方法返回一个 task值在他工作被暂停的时候(步骤3,6).当异步方法完成了他的工作之后,task被标记为complete,工作的结果也保存在task之中。

异步的API方法

你可能会想知道在哪里可以找到,如GetStringAsync,支持异步编程的API方法。.NET Framework 4.5包含许多使用await和async工作的成员方法。识别这些方法很简单,方法名都是以”Async”结尾的并且返回类型都是Task或者 Task<TResult>。例如,System.IO.Stream类包含的方法,如CopyToAsync,ReadAsync,及 WriteAsync的对应的同步方法是CopyTo,Read和Write。

线程

异步方法的目的是不阻塞操作。在async方法中, await任务在执行的过程中,并不会阻塞当前的线程,其余的方法可以继续执行,控制权将会移交到async方法的调用者。

async和await关键字并不会创建额外的线程,async方法不会去请求多线程操作。真正创建线程的操作是由Task.Run()实现的,一个async方法并不是在他自己的线程上执行的,只有当方法被激活时,才会使用当前线程的上下文和处理时间。

async方法要比BackgroundWorker更实用,而且使用起来更简单而且不用去过多的考虑竞态冲突神马的。async方法会将运行中的代码依据某些算法进行合理的拆分,并传递给线程池,这也是BackgroundWorker不能比的。

Async和Await

如果需要使用async或者await指定一个异步方法,我们需要注意一下两点:

用async标记的异步方应该使用await关键子来制定挂起点。await操作符会告诉编译器,这个async方放在完成之前,后面的代码无法继续执行,同时,控制权转移到async方法的调用者。

标记为async的方法,调用时应使用await。

一个async方法里通常包含一个或多个的对应的await操作符,但如果没有await表达式也不会导致编译错误。但如果调用一个async方 法,却不使用await关键字来标记一个挂起点的话,程序将会忽略async关键字并以同步的方式执行。编译器会对类似的问题发出警告。

async和await都是上下文关键字:更多的细节可以参考:

Async (Visual Basic)

async (C# Reference)

Await Operator (Visual Basic)

await (C# Reference)

返回类型和参数

在.NET Framework编程中,一个async方法通常返回的类型是Task或者Task<TResult>。在异步方法中,await操作符作用于从另外一个异步方法返回的Task。

如果指定Task<TResult>为返回结果,那么这个方法必须包含return指定的TResult结果的语句。

如果使用Task作为返回值,那么这个方法应该不存在使用return语句返回结果的代码,或者返回的结果不参与任何运算(包括赋值操作)。

1 // 明确指定 Task<tresult>
2 async Task<int> TaskOfTResult_MethodAsync()
3 {
4     int hours;
5     // . . .
6     // return一个整数作为结果.
7     return hours;
8 }

10 // 调用 TaskOfTResult_MethodAsync
11 Task<int> returnedTaskTResult = TaskOfTResult_MethodAsync();
12 int intResult = await returnedTaskTResult;
13 // 或者使用一条语句
14 int intResult = await TaskOfTResult_MethodAsync();
15 
16 
17 // 明确指定 Task
18 async Task Task_MethodAsync()
19 {
20     // . . .
21     // 方法没有任何return语句.  
22 }
23 
24 // 调用 Task_MethodAsync
25 Task returnedTask = Task_MethodAsync();
26 await returnedTask;
27 // 或者使用一条语句
28 await Task_MethodAsync();

每一个返回的task都代表一个正在执行的工作,task包装的信息中包含了这个异步方法的执行时的状态,最终的结果,或者处理过程中抛出的异常。

如果返回值为void,这种类型主要使用于定义事件处理。异步事件通常被认为是一系列异步操作的开始。使用void返回类型不需要await,而且调用void异步方法的函数不会捕获方法抛出的异常。

另外,async方法不能使用ref或者out参数,但是可以调用含有这些参数的方法。

命名约定

按照约定,你应该在异步方法的名称后面追加“Async”用以标记此方法。但是在event,基类和接口中不需要遵守约定,就像本文例子中event处理函数Button1_Click一样。

相关主题

一个完整的例子

1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading.Tasks;
6 using System.Windows;
7 using System.Windows.Controls;
8 using System.Windows.Data;
9 using System.Windows.Documents;
10 using System.Windows.Input;
11 using System.Windows.Media;
12 using System.Windows.Media.Imaging;
13 using System.Windows.Navigation;
14 using System.Windows.Shapes;
15 
16  
17 using System.Net.Http;
18 
19 namespace AsyncFirstExample
20 {
21     public partial class MainWindow : Window
22     {
23         // 将event处理函数用async标记,这样就可以在处理函数中使用await实现异步操作. 
24         private async void StartButton_Click(object sender, RoutedEventArgs e)
25         {
26             // 调用和await分离的方式. 
27             //Task<int> getLengthTask = AccessTheWebAsync(); 
28             //// 在这里做一些其他的工作. 
29             //int contentLength = await getLengthTask; 
30 
31             int contentLength = await AccessTheWebAsync();
32 
33             resultsTextBox.Text +=
34                 String.Format("\r\nLength of the downloaded string: {0}.\r\n", contentLength);
35         }
36 
37 
38            // 在签名中三个要注意的事项: 
39     //  - 该方法具有一个async修饰符.  
40     //  - 返回类型为 Task or Task<t>. (参考 "返回类型" 一节.)
41     //    这里, 返回值是 Task<int> 因为返回的是一个整数类型. 
42     //  - 这个方法的名称以 "Async" 结尾.
43     async Task<int> AccessTheWebAsync()
44     { 
45             // 你需要添加System.Net.Http的引用来声明client
46             HttpClient client = new HttpClient();
47 
48             // GetStringAsync 返回 Task<string>. 这意味着当Task结束等待之后 
49             // 你将得到一个string (urlContents).
50             Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");
51 
52             // 你可以做一些不依赖于 GetStringAsync 返回值的操作.
53             DoIndependentWork();
54 
55             // await 操作挂起了当前方法AccessTheWebAsync. 
56             //  - AccessTheWebAsync 直到getStringTask完成后才会继续. 
57             //  - 同时, 控制权将返回 AccessTheWebAsync 的调用者. 
58             //  - 控制权会在getStringTask完成后归还到AccessTheAsync.  
59             //  - await操作将取回getStringTask中返回的string结果. 
60             string urlContents = await getStringTask;
61 
62             // return语句用来指定一个整数结果。
63             // 调用AccessTheWebAsync将会收到一个返回值的长度. 
64             return urlContents.Length;
65     }
66 
67 
68         void DoIndependentWork()
69         {
70             resultsTextBox.Text += "Working . . . . . . .\r\n";
71         }
72     }
73 }
74 
75 // 运行结果: 
76 
77 // Working . . . . . . . 
78 
79 // Length of the downloaded string: 41564.

你可能感兴趣的:(async)