.NET 基于任务的异步模式(Task-based Asynchronous Pattern,TAP) async await
本文内容
- 概述
- 编写异步方法
- 异步程序中的控制流
- API 异步方法
- 线程
- 异步和等待
- 返回类型和参数
- 参考资料
下载 Demo
下载 Demo TPL 与 APM 和 EAP 结合(APM 和 EAP 这两个标准异步方式已经不能适应多核时代,但之前用这两种方式写的代码怎么办?——把它们改造一下,跟 TPL 结合)
概述
异步对可能起阻止作用的活动(例如,应用程序访问 Web 时)至关重要。 对 Web 资源的访问有时很慢或会延迟。 如果此类活动在同步过程中受阻,则整个应用程序必须等待。在异步过程中,应用程序可继续执行不依赖 Web 资源的其他工作,直至潜在阻止任务完成。
下表是利用异步编程能提高响应能力的典型场景。从 .NET Framework 4.5 和 Windows 运行时中列出的 API 包含支持异步编程的方法。
应用程序区域 |
包含异步方法的受支持的 API |
Web 访问 |
HttpClient ,SyndicationClient |
使用文件 |
StorageFile、StreamWriter、StreamReader、XmlReader |
使用图像 |
MediaCapture、BitmapEncoder、BitmapDecoder |
WCF 编程 |
同步和异步操作 |
由于所有与用户界面相关的活动通常共享一个线程,因此,异步对访问 UI 线程的应用程序来说尤为重要。如果任何进程在同步应用程序中受阻,则所有进程都将受阻。 你的应用程序停止响应,因此,你可能在其等待过程中认为它已经失败。
使用异步方法时,应用程序将继续响应 UI。 例如,你可以调整窗口的大小或最小化窗口;如果你不希望等待应用程序结束,则可以将其关闭。
可以使用三种方式来实现 TAP:即手动使用 C# 编译器,或将编译器和手动方法结合使用。使用 TAP 模式来实现计算密集型和 I/O 密集型异步操作。
- 使用编译器。在 Visual Studio 2012 和 .NET Framework 4.5 中,任何具有 async 关键字的方法都被看作是一种异步方法,并且 C# 会执行必要的转换,以通过 TAP 来异步实现该方法。 异步方法应返回 System.Threading.Tasks.Task 或 System.Threading.Tasks.Task<TResult> 对象。
- 手动生成 TAP 方法。也可以手动实现 TAP 模式,以更好地控制实现。编译器依赖从 System.Threading.Tasks 命名空间公开的公共外围应用和 System.Runtime.CompilerServices 命名空间中支持的类型。 如要自己实现 TAP,你需要创建一个 TaskCompletionSource<TResult> 对象、执行异步操作,并在操作完成时,调用 SetResult、SetException、SetCanceled 方法,或调用这些方法之一的Try版本。 手动实现 TAP 方法时,需在所表示的异步操作完成时完成生成的任务。 例如:
- 混合方法。你可能发现手动实现 TAP 模式、但将实现核心逻辑委托给编译器的这种方法很有用。 例如,当你想要验证编译器生成的异步方法之外的实参时,可能需要使用这种混合方法,以便异常可以转义到该方法的直接调用方而不是通过 System.Threading.Tasks.Task 对象被公开:
public static Task<int> ReadTask(this Stream stream, byte[] buffer, int offset, int count, object state)
{
var tcs = new TaskCompletionSource<int>();
stream.BeginRead(buffer, offset, count, ar =>
{
try { tcs.SetResult(stream.EndRead(ar)); }
catch (Exception exc) { tcs.SetException(exc); }
}, state);
return tcs.Task;
}
public Task<int> MethodAsync(string input)
{
if (input == null) throw new ArgumentNullException("input");
return MethodAsyncInternal(input);
}
private async Task<int> MethodAsyncInternal(string input)
{
// code that uses await goes here
return value;
}
本文主要说明“使用编译器”方法。
编写异步方法
C# 中 async 和 await 关键字是异步编程的核心。通过这两个关键字就可以轻松创建异步方法,几乎与创建同步方法一样。如下所示的 WPF 程序,布局文件上有个按钮和文本框:
private async void StartButton_Click(object sender, RoutedEventArgs e)
{
// Call and await separately.
//Task<int> getLengthTask = AccessTheWebAsync();
//// You can do independent work here.
//int contentLength = await getLengthTask;
int contentLength = await AccessTheWebAsync();
resultsTextBox.Text +=
String.Format("\r\nLength of the downloaded string: {0}.\r\n", contentLength);
}
// Three things to note in the signature:
// - The method has an async modifier.
// - The return type is Task or Task<T>. (See "Return Types" section.)
// Here, it is Task<int> because the return statement returns an integer.
// - The method name ends in "Async."
async Task<int> AccessTheWebAsync()
{
// You need to add a reference to System.Net.Http to declare client.
HttpClient client = new HttpClient();
// GetStringAsync returns a Task<string>. That means that when you await the
// task you'll get a string (urlContents).
Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");
// You can do work here that doesn't rely on the string from GetStringAsync.
DoIndependentWork();
// The await operator suspends AccessTheWebAsync.
// - AccessTheWebAsync can't continue until getStringTask is complete.
// - Meanwhile, control returns to the caller of AccessTheWebAsync.
// - Control resumes here when getStringTask is complete.
// - The await operator then retrieves the string result from getStringTask.
string urlContents = await getStringTask;
// The return statement specifies an integer result.
// Any methods that are awaiting AccessTheWebAsync retrieve the length value.
return urlContents.Length;
}
void DoIndependentWork()
{
resultsTextBox.Text += "Working . . . . . . .\r\n";
}
执行结果:
Working . . . . . . .
Length of the downloaded string: 41609.
说明:
1,当程序访问网络时,无论你如何拖拽、最大化最小化、如何点击,UI 都不会失去响应;
2,“async Task<int> AccessTheWebAsync()”方法签名,有三点需要注意:1)有 async 修饰符;2)返回类型是 Task 或 Task<int>。该方法是 Task<int>,因为它返回的是链接内容的大小;3)方法名以 Async 结尾;
3,“string urlContents = await getStringTask;”语句,有四点需要注意:1)AccessTheWebAsync 方法直到 getStringTask 完成才能继续;2)同时,控制流返回到 AccessTheWebAsync 的调用者;3)getStringTask 完成后,控制流才会恢复;4)之后,await 操作符从 getStringTask 检索结果。
下面总结让一个示例成为异步方法的特征:
- 方法签名包含一个 async 修饰符。
- 按照约定,异步方法的名称以“Async”后缀结尾。
- 返回类型为下列类型之一:
- 如果你的方法有 TResult 类型的返回语句,则为 Task<TResult>。
- 如果你的方法没有返回语句,则为 Task。
- 如果你编写的是异步事件处理程序,则为 Void(Visual Basic 中为 Sub)。
- 方法通常包含至少一个 await 表达式,该表达式标记一个点,在该点上,直到等待的异步操作完成方法才能继续。同时,将方法挂起,并且控件返回到方法的调用方。
在异步方法中,可使用提供的关键字和类型来指示需要完成的操作,且编译器会完成其余操作,其中包括持续跟踪控件以挂起方法返回等待点时发生的情况。 一些常规流程(例如,循环和异常处理)在传统异步代码中处理起来可能很困难。 在异步方法中,元素的编写频率与同步解决方案相同且此问题得到解决。
异步程序中的控制流
异步编程中最需弄清的是控制流是如何从方法移动到方法。
private async void StartButton_Click(object sender, RoutedEventArgs e)
{
// Call and await separately.
//Task<int> getLengthTask = AccessTheWebAsync();
//// You can do independent work here.
//int contentLength = await getLengthTask;
resultsTextBox.Text += "1: Entering startButton_Click.\r\n" +
" Calling AccessTheWebAsync.\r\n";
int contentLength = await AccessTheWebAsync();
resultsTextBox.Text +=
String.Format("\r\n6: Length of the downloaded string: {0}.\r\n", contentLength);
}
async Task<int> AccessTheWebAsync()
{
resultsTextBox.Text += "\r\n2: Entering AccessTheWebAsync.";
HttpClient client = new HttpClient();
resultsTextBox.Text += "\r\n Calling HttpClient.GetStringAsync.\r\n";
Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");
DoIndependentWork();
resultsTextBox.Text += "\r\n4: Back in startButton_Click.\r\n" +
" Task getStringTask is started.\r\n";
string urlContents = await getStringTask;
resultsTextBox.Text += "\r\n5: Back in AccessTheWebAsync." +
"\r\n Task getStringTask is complete." +
"\r\n Processing the return statement." +
"\r\n Exiting from AccessTheWebAsync.\r\n";
return urlContents.Length;
}
void DoIndependentWork()
{
resultsTextBox.Text += "\r\n3: Entering DoIndependentWork.\r\n";
resultsTextBox.Text += "\r\n Working . . . . . . .\r\n";
}
运行结果:
1: Entering startButton_Click.
Calling AccessTheWebAsync.
2: Entering AccessTheWebAsync.
Calling HttpClient.GetStringAsync.
3: Entering DoIndependentWork.
Working . . . . . . .
4: Back in startButton_Click.
Task getStringTask is started.
5: Back in AccessTheWebAsync.
Task getStringTask is complete.
Processing the return statement.
Exiting from AccessTheWebAsync.
6: Length of the downloaded string: 41609.
再稍微复杂点:
private async void startButton_Click(object sender, RoutedEventArgs e)
{
// The display lines in the example lead you through the control shifts.
resultsTextBox.Text += "ONE: Entering startButton_Click.\r\n" +
" Calling AccessTheWebAsync.\r\n";
Task<int> getLengthTask = AccessTheWebAsync();
resultsTextBox.Text += "\r\nFOUR: Back in startButton_Click.\r\n" +
" Task getLengthTask is started.\r\n" +
" About to await getLengthTask -- no caller to return to.\r\n";
int contentLength = await getLengthTask;
resultsTextBox.Text += "\r\nSIX: Back in startButton_Click.\r\n" +
" Task getLengthTask is finished.\r\n" +
" Result from AccessTheWebAsync is stored in contentLength.\r\n" +
" About to display contentLength and exit.\r\n";
resultsTextBox.Text +=
String.Format("\r\nLength of the downloaded string: {0}.\r\n", contentLength);
}
async Task<int> AccessTheWebAsync()
{
resultsTextBox.Text += "\r\nTWO: Entering AccessTheWebAsync.";
// Declare an HttpClient object and increase the buffer size. The default
// buffer size is 65,536.
HttpClient client =
new HttpClient() { MaxResponseContentBufferSize = 1000000 };
resultsTextBox.Text += "\r\n Calling HttpClient.GetStringAsync.\r\n";
// GetStringAsync returns a Task<string>.
Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");
resultsTextBox.Text += "\r\nTHREE: Back in AccessTheWebAsync.\r\n" +
" Task getStringTask is started.";
// AccessTheWebAsync can continue to work until getStringTask is awaited.
resultsTextBox.Text +=
"\r\n About to await getStringTask and return a Task<int> to startButton_Click.\r\n";
// Retrieve the website contents when task is complete.
string urlContents = await getStringTask;
resultsTextBox.Text += "\r\nFIVE: Back in AccessTheWebAsync." +
"\r\n Task getStringTask is complete." +
"\r\n Processing the return statement." +
"\r\n Exiting from AccessTheWebAsync.\r\n";
return urlContents.Length;
}
运行结果:
ONE: Entering startButton_Click.
Calling AccessTheWebAsync.
TWO: Entering AccessTheWebAsync.
Calling HttpClient.GetStringAsync.
THREE: Back in AccessTheWebAsync.
Task getStringTask is started.
About to await getStringTask and return a Task<;int> to startButton_Click.
FOUR: Back in startButton_Click.
Task getLengthTask is started.
About to await getLengthTask -- no caller to return to.
FIVE: Back in AccessTheWebAsync.
Task getStringTask is complete.
Processing the return statement.
Exiting from AccessTheWebAsync.
SIX: Back in startButton_Click.
Task getLengthTask is finished.
Result from AccessTheWebAsync is stored in contentLength.
About to display contentLength and exit.
Length of the downloaded string: 41635.
API 异步方法
如何找到像 GetStringAsync 这样支持异步编程的方法。 .NET Framework 4.5 包含使用 async 和 await 的许多成员,它们都已“Async”为后缀和 Task 或 Task<TResult> 的返回类型。 例如,System.IO.Stream 类包含的方法 CopyToAsync、ReadAsync、WriteAsync 等方法以及同步方法 CopyTo、Read 和 Write。
线程
异步方法旨在成为非阻止操作。异步方法中的 await 表达式在等待的任务正在运行时,不会阻止当前线程。相反,表达式在继续时,注册方法的其余部分并将控件返回到异步方法的调用方。
async 和 await 关键字不会导致创建其他线程。因为异步方法不会在其自身线程上运行,因此它不需要多线程。 只有当方法处于活动状态时,该方法将在当前同步上下文中运行并使用线程上的时间。 可以使用 Task.Run 将占用大量 CPU 的工作移到后台线程,但是后台线程不会帮助正在等待结果的进程变为可用状态。
对于异步编程而言,该基于异步的方法优于几乎每个用例中的现有方法。 具体而言,此方法比 BackgroundWorker 更适用于 IO 绑定的操作,因为此代码更简单且无需防止争用条件。 结合 Task.Run 使用时,异步编程比 BackgroundWorker 更适用于 CPU 绑定的操作,因为异步编程将运行代码的协调细节与 Task.Run 传输至线程池的工作区分开来。
异步和等待
如果通过 async 修饰符指定某种方法为异步方法,则可以启用以下两个功能。
- 标记的异步方法可以使用 await 来指定悬挂点。await 运算符通知编译器异步方法只有直到等待的异步过程完成才能继续通过该点。 同时,控件返回至异步方法的调用方。 await 表达式中异步方法的挂起不能使该方法退出,并且 finally 块不会运行。
- 标记的异步方法本身可以通过调用它的方法等待。
异步方法通常包含 await 运算符的一个或多个匹配项,但缺少 await 表达式不会导致编译器错误。 如果异步方法未使用 await 运算符标记悬挂点,则该方法将作为同步方法执行,不管异步修饰符如何。编译器将为此类方法发布一个警告。
Async 、async、Await 和 await 都是上下文关键字。 有关更多信息和示例,请参见以下主题:
返回类型和参数
.NET Framework 异步编程中异步方法通常返回 Task 或 Task<TResult>。 在异步方法中,await 运算符应用于通过调用另一个异步方法返回的任务。
如果方法包含 Return (Visual Basic) 或指定类型 TResult 的操作数的 return (C#) 语句,则将 Task<TResult> 指定为返回类型。
如果方法不含任何 return 语句或包含不返回操作数的 return 语句,则将 Task 用作返回类型。
下面的示例演示如何声明并调用可返回 Task<TResult> 或 Task 的方法。
// Signature specifies Task<;TResult>
async Task<;int> TaskOfTResult_MethodAsync()
{
int hours;
// . . .
// Return statement specifies an integer result.
return hours;
}
// Calls to TaskOfTResult_MethodAsync
Task<;int> returnedTaskTResult = TaskOfTResult_MethodAsync();
int intResult = await returnedTaskTResult;
// or, in a single statement
int intResult = await TaskOfTResult_MethodAsync();
// Signature specifies Task
async Task Task_MethodAsync()
{
// . . .
// The method has no return statement.
}
// Calls to Task_MethodAsync
Task returnedTask = Task_MethodAsync();
await returnedTask;
// or, in a single statement
await Task_MethodAsync();
每个返回的任务表示正在进行的工作。 任务可封装有关异步进程状态的信息,如果未成功,则最后会封装来自进程的最终结果或进程引发的异常。
异步方法还可以是 Sub 方法 (Visual Basic) 或具有 void 返回类型 (C#)。 该返回类型主要用于定义需要 void 返回类型的事件处理程序。 异步事件处理程序通常用作异步程序的起始点。
无法等待为 Sub 程序或具有 void 返回类型的异步方法,并且无效的返回方法的调用方无法捕获该方法引发的任何异常。
异步方法无法声明 Visual Basic 中的 ByRef 参数或 C# 中的 ref 或 out 参数,但此方法可以调用具有此类参数的方法。
有关更多信息和示例,请参见异步返回类型(C# 和 Visual Basic)。 有关如何在异步方法中捕捉异常的更多信息,请参见 try-catch(C# 参考)或 Try...Catch...Finally 语句 (Visual Basic)。
Windows 运行时编程中的异步 API 具有下列返回类型之一,它类似于任务:
- IAsyncOperation,它对应于 Task<TResult>
- IAsyncAction,它对应于 Task
- IAsyncActionWithProgress
- IAsyncOperationWithProgress
参考资料
.NET 基于任务的异步模式(Task-based Asynchronous Pattern,TAP) async await的更多相关文章
- C#5.0之后推荐使用TPL(Task Parallel Libray 任务并行库) 和PLINQ(Parallel LINQ, 并行Linq). 其次是TAP(Task-based Asynchronous Pattern, 基于任务的异步模式)
学习书籍: <C#本质论> 1--C#5.0之后推荐使用TPL(Task Parallel Libray 任务并行库) 和PLINQ(Parallel LINQ, 并行Linq). 其次是 ...
- Event-based Asynchronous Pattern Overview基于事件的异步模式概览
https://msdn.microsoft.com/zh-cn/library/wewwczdw(v=vs.110).aspx Applications that perform many task ...
- C#基于任务的异步模式
using System; using System.Threading; using System.Threading.Tasks; using static System.Console; //异 ...
- C# 基于任务的异步模式的创建与使用的简单示例
对于窗体程序,使用基于任务的异步模式需要用到Task类,下面示例下非常简单的用法. 1.创建一个拥有异步方法的类 该类拥有一个异步方法DoSomthingAsync,根据微软建议的命名规则该方法要带A ...
- 【温故知新】C#基于事件的异步模式(EAP)
在开发winform和调用asp.net的web service引用的时候,会出现许多命名为 MethodNameAsync 的方法. 例如: winform的按钮点击 this.button1.Cl ...
- 三、基于任务的异步模式(TAP),推荐使用
一.引言 在上两个专题中我为大家介绍.NET 1.0中的APM和.NET 2.0中的EAP,在使用前面两种模式进行异步编程的时候,大家多多少少肯定会感觉到实现起来比较麻烦, 首先我个人觉得,当使用AP ...
- 基于任务的异步编程模式,Task-based Asynchronous Pattern
术语: APM 异步编程模型,Asynchronous Programming Model,其中异步操作由一对 Begin/End 方法(如 FileStream.BeginRea ...
- C#异步编程之基于任务的异步模式
http://www.cnblogs.com/afei-24/p/6757361.html该文讲了基于任务的编程,这里再详细介绍一下.一.延续任务 private async static void ...
- 基于事件的异步模式(EAP)
什么是EAP异步编程模式 EAP基于事件的异步模式是.net 2.0提出来的,实现了基于事件的异步模式的类将具有一个或者多个以Async为后缀的方法和对应的Completed事件,并且这些类都支持异步 ...
随机推荐
- chrome ui源码剖析-Accelerator(快捷键)
好久没有自己写东西了,chrome有着取之不尽的技术精华供学习,记录一下. 源码目录: http://src.chromium.org/viewvc/chrome/trunk/src/ui/bas ...
- WebLogic使用总结(五)——Web项目使用Sigar在WebLogic服务器部署遇到的问题
今天在WebLogic 12c服务器上部署Web项目时,碰到了一个问题.项目中使用到了"Sigar.jar"监控Window平台下的cpu使用率.内存使用率和硬盘信息,sigar. ...
- 使用Visual Studio 2012远程调试Windows Azure网站
登录Windows Azure门户,点击"所有项目"中的网站名称. 点击"配置". 在"远程调试"选项中选择"打开",在 ...
- android编译错误“OnClickListener cannot be resolved to a type”
在android代码编译时可能会出现如下错误: 部分代码: <span style="font-size:18px;">public void onCreate(Bun ...
- Java io.netty.util.ReferenceCountUtil 代码实例
原文:https://www.helplib.com/Java_API_Classes/article_64580 以下是展示如何使用io.netty.util.ReferenceCountUtil的 ...
- C#编程(六十二)---------LINQ标准的查询操作符
LINQ标准的查询操作符 首先我们来看一下LINQ的操作符,可根据查询操作符的操作”类型”进行分类,如把它们分成投影,限制,排序,联接,分组,串联,聚合,集合,生成,转换,元素,相等,量词,分割等. ...
- C#编程(五十四)----------Lookup类和有序字典
原文链接: http://blog.csdn.net/shanyongxu/article/details/47071607 Lookup类 Dictionary<Tkey,TValue> ...
- Arcgis Pro为什么我已经安装了汉化包但是显示的还是英文?
- java过滤特殊字符的正则表达式
// 过滤特殊字符 public staticString StringFilter(String str) throws PatternSyntaxException { // 只允许字母和数字 / ...
- Android:客户端和服务器之间传输数据加密
Android客户端与服务器进行数据传输时,一般会涉及到两类数据的加密情况,一类是只有创建者才能知道的数据,比如密码:另一类是其他比较重要的,但是可以逆向解密的数据. 第一类:密码类的数据,为了让用户 ...