利用 async & await 的异步编程
Posted DotNet
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了利用 async & await 的异步编程相关的知识,希望对你有一定的参考价值。
来源:反骨仔(二五仔)
链接:cnblogs.com/liqingwen/p/5922573.html
一、异步编程的简介
通过使用异步编程,你可以避免性能瓶颈并增强应用程序的总体响应能力。
Visual Studio 2012 引入了一个简化的方法,异步编程,在 .NET Framework 4.5 和 Windows 运行时利用异步支持。编译器可执行开发人员曾进行的高难度工作,且应用程序保留了一个类似于同步代码的逻辑结构。因此,您仅需要进行一小部分工作就可以获得异步编程的所有优点。
二、异步提高响应能力
异步对可能引起阻塞的活动(例如应用程序访问 Web 时)至关重要。对 Web 资源的访问有时很慢或会延迟。如果此类活动在同步过程中受阻,则整个应用程序必须等待。 在异步过程中,应用程序可继续执行不依赖 Web 资源的其他工作,直至潜在阻塞的任务完成。
下图显示了异步编程提高响应能力的典型应用场景。包含从 .NET Framework 4.5 和 Windows 运行时中列出的一些包含支持异步编程的方法的类。
由于所有与用户界面相关的活动通常共享一个线程,因此,异步对访问 UI 线程的应用程序来说尤为重要。 如果在一个同步应用程序中有任何的线程被阻塞了,那么所有线程都将被阻塞,再严重一点,你的应用程序将会停止响应。
使用异步方法时,应用程序将继续响应 UI。例如,你可以调整窗口的大小或最小化窗口;如果你不希望等待应用程序结束,则可以将其关闭。
三、更容易编写的异步方法
C# 中的 async 和 await 关键字都是异步编程的核心。通过使用这两个关键字,你可以使用 .NET framework 或 Windows 运行时中的资源轻松创建异步方法(几乎与创建同步方法一样轻松)。
下面的示例演示了一种使用 async 和 await 定义的异步方法。
/// <summary>
/// 异步访问 Web
/// </summary>
/// <returns></returns>
/// <remarks>
/// 方法签名的 3 要素:
/// ① async 修饰符
/// ② 返回类型 Task 或 Task<TResult>:这里的 Task<int> 表示 return 语句返回 int 类型
/// ③ 方法名以 Async 结尾
/// </remarks>
async Task<int> AccessTheWebAsync()
{
//记得 using System.Net.Http 哦
var client = new HttpClient();
//执行异步方法 GetStringAsync
Task<string> getStringTask = client.GetStringAsync("http://www.google.com.hk/");
//假设在这里执行一些非异步的操作
DoIndependentWork();
//等待操作挂起方法 AccessTheWebAsync
//直到 getStringTask 完成,AccessTheWebAsync 方法才会继续执行
//同时,控制将返回到 AccessTheWebAsync 方法的调用方
//直到 getStringTask 完成后,将在这里恢复控制。
//然后从 getStringTask 拿到字符串结果
string urlContents = await getStringTask;
//返回字符串的长度(int 类型)
return urlContents.Length;
}
如果 AccessTheWebAsync 在调用 GetStringAsync 时没有其它操作,你可以用这样的方式来简化代码。
string urlContents = await client.GetStringAsync("http://www.google.com.hk/");
根据以上代码进行简单总结:
(1)方法签名包含一个 async 修饰符。
(2)按照约定,异步方法的名称以“Async”后缀为结尾。
(3)返回类型为下列类型之一:
① 如果你的方法有操作数为 TResult 类型的返回语句,则为 Task<TResult>。
② 如果你的方法没有返回语句或具有没有操作数的返回语句,则为 Task。
③ 如果你编写的是异步事件处理程序,则为 void。
(4)方法通常包含至少一个 await 表达式,该表达式标记一个点,在该点上,直到等待的异步操作完成方法才能继续。 同时,将方法挂起,并且控制权将返回到方法的调用方。
在异步方法中,可使用提供的关键字和类型来指示需要完成的操作,且编译器会完成其余操作。
四、异步方法的控制流(核心)
异步编程中最需弄清的是控制流,即如何从一个方法移动到另一个方法, 请用一颗感恩的心来观察下图。
步骤解析:
① 事件处理程序调用并等待 AccessTheWebAsync 异步方法。
② AccessTheWebAsync 创建 HttpClient 对象并调用它的 GetStringAsync 异步方法来下载网站内容。
③ 假设 GetStringAsync 中发生了某种情况,该情况挂起了它的进程。可能必须等待网站下载或一些其他阻塞的活动。为避免阻塞资源,GetStringAsync 会将控制权出让给其调用方 AccessTheWebAsync。GetStringAsync 返回 Task,其中 TResult 为字符串,并且 AccessTheWebAsync 将任务分配给 getStringTask 变量。该任务表示调用 GetStringAsync 的正在进行的进程,其中承诺当工作完成时产生实际字符串值。
④ 由于尚未等待 getStringTask,因此,AccessTheWebAsync 可以继续执行不依赖于 GetStringAsync 得出最终结果的其他任务。该任务由对同步方法 DoIndependentWork 的调用表示。
⑤ DoIndependentWork 是完成其工作并返回其调用方的同步方法。
⑥ AccessTheWebAsync 已完成工作,可以不受 getStringTask 的结果影响。 接下来,AccessTheWebAsync 需要计算并返回该下载字符串的长度,但该方法仅在具有字符串时才能计算该值。因此,AccessTheWebAsync 使用一个 await 运算符来挂起其进度,并把控制权交给调用 AccessTheWebAsync 的方法。AccessTheWebAsync 将 Task<int> 返回至调用方。 该任务表示对产生下载字符串长度的整数结果的一个承诺。
【备注】如果 GetStringAsync(即 getStringTask)在 AccessTheWebAsync 等待前完成,则控制权会保留在 AccessTheWebAsync 中。 如果异步调用过程 (getStringTask) 已完成,并且 AccessTheWebSync 不必等待最终结果,则挂起然后返回到 AccessTheWebAsync,但这会造成成本的浪费。
在调用方内部(假设这是一个事件处理程序),处理模式将继续。在等待结果前,调用方可以开展不依赖于 AccessTheWebAsync 结果的其他工作,否则就需等待片刻。事件处理程序等待 AccessTheWebAsync,而 AccessTheWebAsync 等待 GetStringAsync。
⑦ GetStringAsync 完成并生成一个字符串结果。 字符串结果不是通过你预期的方式调用 GetStringAsync 所返回的。(请记住,此方法已在步骤 3 中返回一个任务。)相反,字符串结果存储在表示完成方法 getStringTask 的任务中。 await 运算符从 getStringTask 中检索结果。赋值语句将检索到的结果赋给 urlContents。
⑧ 当 AccessTheWebAsync 具有字符串结果时,该方法可以计算字符串长度。然后,AccessTheWebAsync 工作也将完成,并且等待事件处理程序可继续使用。
你可以尝试思考一下同步行为和异步行为之间的差异。当其工作完成时(第 5 步)会返回一个同步方法,但当其工作挂起时(第 3 步和第 6 步),异步方法会返回一个任务值。在异步方法最终完成其工作时,任务会标记为已完成,而结果(如果有)将存储在任务中。
五、线程
异步方法旨在成为非阻塞操作。异步方法中的 await 表达式在等待的任务正在运行时不会阻塞当前线程。相反,表达式在继续时注册方法的其余部分并将控制权返回到异步方法的调用方。
async 和 await 关键字不会导致创建其他线程。因为异步方法不会在其自身线程上运行,因此它不需要多线程。只有当方法处于活动状态时,该方法将在当前同步上下文中运行并使用线程上的时间。可以使用 Task.Run 将占用大量 CPU 的工作移到后台线程,但是后台线程不会帮助正在等待结果的进程变为可用状态。
对于异步编程而言,该基于异步的方法优于几乎每个用例中的现有方法。具体而言,此方法比 BackgroundWorker 更适用于 IO 绑定的操作,因为此代码更简单且无需防止抢先争用条件。
结合 Task.Run 使用时,异步编程比 BackgroundWorker 更适用于 CPU 绑定的操作,因为异步编程将运行代码的协调细节与 Task.Run 传输至线程池的工作区分开来。
六、async 和 await
如果通过使用 async 修饰符指定某种方法为异步方法,则会出现下面两种现象。
标记的异步方法可以使用 await 来指定悬挂点。await 运算符通知编译器异步方法只有直到等待的异步过程完成才能继续通过该点。同时,控制权将返回至异步方法的调用方。
await 表达式中异步方法的挂起不能使该方法退出,并且 finally 块不会运行。
标记的异步方法本身可以通过调用它的方法等待。
异步方法通常包含 await 运算符的一个或多个匹配项,但缺少 await 表达式不会导致编译器错误。如果异步方法未使用 await 运算符标记悬挂点,则该方法将作为同步方法执行,不管异步修饰符如何。编译器将为此类方法发布一个警告。
七、返回类型和参数信息
在 .NET 中,异步方法通常返回 Task 或 Task<TResult>。在异步方法中,await 运算符应用于通过调用另一个异步方法返回的任务。
如果方法包含 指定类型 TResult 的操作数的 return 语句,则将 Task<TResult> 指定为返回类型。
如果方法不含任何 return 语句或包含不返回操作数的 return 语句,则将 Task 用作返回类型。
下面的示例演示如何声明并调用可返回 Task 或 Task<TResult> 的方法。
static async Task<Guid> Method1Async() //Task<Guid>
{
var result = Guid.NewGuid();
await Task.Delay(1);
//这里返回一个 Guid 的类型
return result;
}
static async Task Method2Async() //Task
{
//Do...
await Task.Delay(1);
//Do...
//这里没有 return 语句
}
//调用 Method1Async
//方式一
Task<Guid> t1 = Method1Async();
Guid guid1 = t1.Result;
//方式二
Guid guid2 = await Method1Async();
//调用 Method2Async
//方式一
Task t2 = Method2Async();
await t2;
//方式二
await Method2Async();
每个返回的任务表示正在进行的工作。任务可封装有关异步进程状态的信息,如果未成功,则最后会封装来自进程的最终结果或进程引发的异常。
异步方法还可以是具有 void 返回类型。该返回类型主要用于定义需要 void 返回类型的事件处理程序。异步事件处理程序通常用作异步程序的起始点。
无法等待具有 void 返回类型的异步方法,并且一个 void 返回值的调用方无法捕获该方法引发的任何异常。
异步方法无法声明 C# 中的 ref 或 out 参数,但此方法可以调用具有此类参数的方法。
八、命名的约定
根据约定,将“Async”追加到具有 async 修饰符的方法名称。
如果某一约定中的事件、基类或接口协定建议其他名称,则可以忽略此约定。例如,你不应重命名常用事件处理程序,例如 btnOpen_Click。
传送门
(推荐)
关注「DotNet」
看更多精选 .Net 技术文章
↓↓↓
以上是关于利用 async & await 的异步编程的主要内容,如果未能解决你的问题,请参考以下文章
JavaScript的ES6中async&&await的简单使用以及介绍