背景
自.NET 4.5发布以来已经有很长一段时间了。留在了我们的记忆里,其发布在2012年8月15日。是的,六年前。感觉老了吗?好吧,我不打算让你做出改变,而是提醒你一些.NET发布的亮点。此版本带来的主要功能之一是使用async / await方法进行异步编程。基本上,微软的团队通过保持类似于同步代码的逻辑结构,使编译器完成开发人员过去经常做的工作。
你看,在那个时候,Windows Phone仍然是一件事情,为这些平台开发应用程序有一定的局限性。主要原因是Windows Phone与桌面应用程序不同,引入了硬限制,其中任何方法都无法阻塞超过50ms。反过来,这意味着开发人员不再需要阻止UI,这导致了代码中某种异步性的必要性。.NET 4.5展示了对这种必要性的回应。
那么,为什么我要写一些超过五年前发生过的事情呢?好吧,我注意到尽管这是一个古老的话题,仍然有很多工程师都在努力解决这个问题。引用Mike James在iProgrammer中的话:
通常,程序员完全清楚他们正在做的是面向对象的,但只是模糊地意识到他们正在编写异步代码。
这就是为什么我会尝试在一些博客文章中总结这种编程风格中最重要的部分。此外,我们将尝试确定我们应该使用这种编程风格的情况,以及我们应该避免它的情况。所以,让我们潜入它吧!
动机和使用案例
从本质上讲,这种编程风格适用于您需要能够在等待其他事情完成时做某些事情的任何地方。多年来使用这个工具实际上让我们有能力意识到应该在哪里使用它。它非常适合用户体验,通常用于基于事件的系统,例如基于CPU的并行性或使用文件。
任何可能阻止的活动,例如访问Web,都是此方法的良好候选者。如果这些阻止活动中的任何一个在同步过程中结束,则会阻止整个应用程序。另一方面,如果此活动是异步进程的一部分,则应用程序可以继续处理其他任务,直到活动完成。
当然,你不需要滥用这种能力。例如,我们不应异步更新依赖的记录。这通常是一个坏主意,我们的数据将很快失去同步。除此之外,这个概念有时被过度使用。例如,如果我们正在研究一些简单的行动和操作,我们应该考虑更正统的方法。事实上,在这些情况下使用异步概念可能会带来更多的开销而不是收益。
Async/Await和返回类型
异步机制是在我们的代码中使用async / await关键字在.NET中实现的。我们使用async运算符将我们的方法标记为异步。只有具有此运算符方法的方法才能在其中使用 await运算符。 另一方面,await运算符告诉编译器,在等待异步任务完成之前,使用它的异步方法不能继续超过该点(即await标识的语句行)。基本上,它会暂停方法的执行,直到等待完成任务为止。也可以使用其他方法中的await 运算符调用使用async运算符标记的方法。
另一个重要的事情是异步方法必须返回Task类或Task<TResult>类。这是因为在此方法中,await运算符应用于从另一个异步方法返回的 Task。总结一下,异步机制在.NET中实现如下:
- 我们使用async运算符来标记我们想要异步的方法。这些方法必须返回Task类或Task<TResult>类。
- 当我们调用用async运算符标记的方法时,我们使用await运算符。此运算符将运行异步方法返回的任务。
如何查看代码呢?看看这个示例WebAccess.cs类:
这个类的目标是以非阻塞的方式访问此网站,并获得响应体的长度,为此 AccessRubiksCodeAsync 方法被使用。我们将 Async后缀放在函数名称的末尾,这是异步方法的标准命名约定。AccessRubiksCodeAsync方法正在调用一些同步的操作,就像函数 LogToConsole一样。 重要的是要意识到GetStringAsync方法也是异步的并且它返回Task。 此Tas k稍后由await运算符运行。
注意我们如何获得同步代码的结构,这很棒。即使我们调用await运算符并且在根本上运行了异步任务,我们的代码看起来也非常简洁。这是async / await机制获得大量普及的原因之一。
那么,如果GetStringAsync方法需要很长时间才能应答,会发生什么?以下是工作流如何运行的:
在第一步(标记为1)中,AccessRubiksCodeAsync方法创建HttpClient的实例并调用此类的GetStringAsync方法。目标是以字符串的形式下载网站的内容。如果发生阻塞GetStringAsync 方法的意外事件,例如,该网站下载时间太长,此方法可以控制其调用者(AccessRubiksCodeAsync)。这就是它避免阻塞资源的方式。除此之外,此方法返回Task<int>,AccessRubiksCodeAsync 将其分配给变量getContent。稍后在代码中,此变量与await 运算符结合使用。
现在,由于我们尚未在getContent 上使用await运算符,因此AccessRubiksCodeAsync方法可以继续执行其他操作,而这些操作不依赖getContent任务结果的。因此,LogToConsole 方法可以以同步方式运行(步骤2)。这意味着此方法将采取控制,完成其工作,然后将控制权交还给AccessRubiksCodeAsync方法。
之后,此方法使用await运算符调用getContent(步骤3)。这意味着此时此方法需要GetStringAsync方法的结果。如果此GetStringAsync方法仍未就绪,则AccessRubiksCodeAsync 将暂停其进度并将控制权返回给其调用者。当然,拥有这种流程的好处是我们“给了一些时间”给GetStringAsync方法,同时,我们运行代码的同步部分。下载内容时,结果返回其长度(步骤4)。
单元测试异步方法
单元测试实际上是如何启动async方法的一个很好的例子。在这个例子中,我使用了xUnit,但是async / await 机制有莪支持其他单元测试框架(如NUnit和MSTests)。如果要将xUnit安装到项目中,请在程序包管理器控制台中输入以下命令:
Install-Package xunit Install-Package xunit.runner.console Install-Package xunit.runner.visualstudio Update-Package
好的,这可以让你快速掌握xUnit。现在让我们看一下WebAccess 类的测试类-WebAcces sTests。
现在,让我们分析一下这个测试的重要部分。首先,请注意我们的测试方法如何使用public async Task进行标记,这与标准的public void不同。这是以如下方式完成的:因为在我们的测试方法中,我们实际上是使用await 运算符调用WebAccess类的AccessRubiksCodeAsync方法。我们可以跳过这个方式并在没有await运算符的情况下调用此方法,并保持public void概念,但结果将不是我们预期的。
如果我们这样做会发生什么——在没有await 运算符的情况下调用async方法?那么,在这种情况下,这个异步方法将作为同步方法执行,这意味着它将阻塞线程。同样,这对于UI和Web操作来说是不利的,并且缩放变得不可能。当然,这在某些情况下可以作为优势使用,但重要的是要知道这种机制是如何工作的。
除此之外,测试结构没有重大变化。我们首先在“arrange”阶段创建WebAccess 类的实例 。在“act”阶段,我们使用await运算符来启动AccessRubiksCodeAsync方法并检索结果。最后,在“assert”阶段,我们检查结果的有效性。
结论
异步编程是.NET多年来的一个有点标准的功能。尽管如此,有时我还是觉得经验不足的程序员不太明白这一点。尤其是那些没有其他技术经验的人,这种机制直接用于技术本身,如Node.js. 或者他们熟悉它并尝试在任何情况下使用它。
原文地址:https://rubikscode.net/2018/05/21/asynchronous-programming-in-net-motivation-and-unit-testing/
原文地址:https://www.cnblogs.com/zhaoshujie/p/11191945.html