通过使用异步编程,可避免出现性能瓶颈,并提高应用程序的整体响应。然而,技术编写异步应用程序的传统方法过于复杂,这使得异步程序难以编写,调试和维护。
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方法中,你会感觉你在 写一个同步的代码,之前的那些困惑已经不复存在了。
在异步方法中发生了什么
了解异步编程最重要的是理解控制权是如何在方法之间转移的。下面的图标将会解释这个过程。
图中的数字对应于下面的步骤。
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 }
9
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.