谈到异步,必然要说下阻塞,在知乎上看到了网友举的例子非常省动,在这里我引用下。
怎样理解阻塞非阻塞与同步异步的区别?
老张爱喝茶,废话不说,煮开水。
出场人物:老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶)。1 老张把水壶放到火上,立等水开。(同步阻塞)老张觉得自己有点傻
2 老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有。(同步非阻塞)老张还是觉得自己有点傻,于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀~~~~的噪音。
3 老张把响水壶放到火上,立等水开。(异步阻塞)老张觉得这样傻等意义不大4 老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞)老张觉得自己聪明了。
所谓同步异步,只是对于水壶而言。
普通水壶,同步;响水壶,异步。
虽然都能干活,但响水壶可以在自己完工之后,提示老张水开了。这是普通水壶所不能及的。
同步只能让调用者去轮询自己(情况2中),造成老张效率的低下。
所谓阻塞非阻塞,仅仅对于老张而言。
立等的老张,阻塞;看电视的老张,非阻塞。
情况1和情况3中老张就是阻塞的,媳妇喊他都不知道。虽然3中响水壶是异步的,可对于立等的老张没有太大的意义。所以一般异步是配合非阻塞使用的,这样才能发挥异步的效用。
有了对异步的理解,接下来我们就进入主题,讲一下Async 和 Await 。
关于这个话题阻塞非阻塞与同步异步,请看这篇文章讲的挺详细的:20分钟读懂进程线程、同步异步、阻塞非阻塞、并发并行
1 含义
C# 5 引入使用 Async 和 Await 的异步编程,C# 中的 Async 和 Await 关键字是异步编程的核心。 通过这两个关键字,可以使用 .NET Framework、.NET Core 或 Windows 运行时中的资源,轻松创建异步方法(几乎与创建同步方法一样轻松)。 使用 async 关键字定义的方法简称为“异步方法”。
2 作用
异步对可能会被屏蔽的活动(如 Web 访问)至关重要。 对 Web 资源的访问有时很慢或会延迟。 如果此类活动在同步过程中被屏蔽,整个应用必须等待。 在异步过程中,应用程序可继续执行不依赖 Web 资源的其他工作,直至潜在阻止任务完成。
下表显示了异步编程提高响应能力的典型区域。 列出的 .NET 和 Windows 运行时 API 包含支持异步编程的方法。
应用程序区域 |
包含异步方法的 .NET 类型 |
包含异步方法的 Windows 运行时类型 |
Web 访问 |
HttpClient |
SyndicationClient |
使用文件 |
StreamWriter, StreamReader, XmlReader |
StorageFile |
使用图像 |
MediaCapture, BitmapEncoder, BitmapDecoder |
|
WCF 编程 |
同步和异步操作 |
由于所有与用户界面相关的活动通常共享一个线程,因此,异步对访问 UI 线程的应用程序来说尤为重要。 如果任何进程在同步应用程序中受阻,则所有进程都将受阻。 你的应用程序停止响应,因此,你可能在其等待过程中认为它已经失败。
使用异步方法时,应用程序将继续响应 UI。 例如,你可以调整窗口的大小或最小化窗口;如果你不希望等待应用程序结束,则可以将其关闭。
3 用法及示例
C# 中的 Async 和 Await 关键字是异步编程的核心。 通过这两个关键字,可以使用 .NET Framework、.NET Core 或 Windows 运行时中的资源,轻松创建异步方法(几乎与创建同步方法一样轻松)。 使用 async 关键字定义的异步方法简称为“异步方法”。
代码如下:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms; // 添加Using指令
using System.Net.Http; namespace async_await
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
/// <summary>
/// 按钮
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void StartButton_Click_Click(object sender, EventArgs e)
{ int contentLength = await AccessTheWebAsync(); resultsTextBox.Text +=
String.Format("\r\nLength of the downloaded string: {0}.\r\n", contentLength);
}
/// <summary>
/// 签名要注意3点
/// 1.必须要有异步修饰符async
/// 2.返回类型必须是 Task 或者 是 Task<T>
/// 3.方法结尾必须是 Async
/// </summary>
/// <returns></returns>
async Task<int> AccessTheWebAsync()
{
//添加Http客户端声明引用
HttpClient client = new HttpClient(); //GetStringAsync返回一个字符串,如果不异步的话,此步可能浪费很长时间 Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com"); //做其他的事
DoIndependentWork(); /*等待操作员暂停访问AccessTheWebAsync ;直到GetStringAsync,才可以继续;与此同时,控制权返回给AccessTheWebAsync;
* 当GetStringAsync 完成时,控制从此处继续 */
string urlContents = await getStringTask; //返回长度
return urlContents.Length;
} void DoIndependentWork()
{
resultsTextBox.Text += "Working . . . . . . .\r\n";
}
}
}
4 异步方法的运行机制
异步编程中最需弄清的是控制流是如何从方法移动到方法的。 下图可引导你完成该过程。
5 关系图中的数值对应于以下步骤。
1 事件处理程序调用并等待 AccessTheWebAsync 异步方法。
2 AccessTheWebAsync 可创建 HttpClient 实例并调用 GetStringAsync 异步方法以下载网站内容作为字符串。
3 GetStringAsync 中发生了某种情况,该情况挂起了它的进程。 可能必须等待网站下载或一些其他阻止活动。 为避免阻止资源,GetStringAsync 会将控制权出让给其调用方 AccessTheWebAsync。
GetStringAsync 返回 Task,其中 TResult 为字符串,并且 AccessTheWebAsync 将任务分配给 getStringTask 变量。 该任务表示调用 GetStringAsync 的正在进行的进程,其中承诺当工作完成时产生实际字符串值。
4 由于尚未等待 getStringTask,因此,AccessTheWebAsync 可以继续执行不依赖于 GetStringAsync 得出的最终结果的其他工作。 该任务由对同步方法 DoIndependentWork 的调用表示。
5 DoIndependentWork 是完成其工作并返回其调用方的同步方法。
6 AccessTheWebAsync 已用完工作,可以不受 getStringTask 的结果影响。 接下来,AccessTheWebAsync 需要计算并返回该下载字符串的长度,但该方法仅在具有字符串时才能计算该值。
因此,AccessTheWebAsync 使用一个 await 运算符来挂起其进度,并把控制权交给调用 AccessTheWebAsync 的方法。 AccessTheWebAsync 将 Task(Of Integer) 或 Task<int> 返回至调用方。 该任务表示对产生下载字符串长度的整数结果的一个承诺
说明:
如果 GetStringAsync在 AccessTheWebAsync 等待前完成,则控件会保留在 AccessTheWebAsync 中。 如果异步调用过程 (getStringTask) 已完成,并且 AccessTheWebSync 不必等待最终结果,则挂起然后返回到 AccessTheWebAsync 将造成成本浪费。
在调用方内部(此示例中的事件处理程序),处理模式将继续。 在等待结果前,调用方可以开展不依赖于 AccessTheWebAsync 结果的其他工作,否则就需等待片刻。事件处理程序等待 AccessTheWebAsync,而 AccessTheWebAsync 等待 GetStringAsync。
7 GetStringAsync 完成并生成一个字符串结果。 字符串结果不是通过按你预期的方式调用 GetStringAsync 所返回的。(请记住,此方法已在步骤 3 中返回一个任务。)相反,字符串结果存储在表示完成方法 getStringTask 的任务中。 await 运算符从 getStringTask 中检索结果。 赋值语句将检索到的结果赋给 urlContents。
8 当 AccessTheWebAsync 具有字符串结果时,该方法可以计算字符串长度。 然后,AccessTheWebAsync 工作也将完成,并且等待事件处理程序可继续使用。
推荐阅读:【Async/Await异步编程中的最佳做法】【走进异步编程的世界 - 开始接触 async/await】【Async and Await】