.NET中的异步编程——动机和单元测试

背景

自.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中实现如下:

  1. 我们使用async运算符来标记我们想要异步的方法。这些方法必须返回Task类或Task<TResult>类。
  2. 当我们调用用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

时间: 2024-10-03 00:14:16

.NET中的异步编程——动机和单元测试的相关文章

Async in C# 5.0(C#中的异步编程Async) 蜗牛翻译之第一章

p { display: block; margin: 3px 0 0 0; } --> 写在前面 在学异步,有位园友推荐了<async in C#5.0>,没找到中文版,恰巧也想提高下英文,用我拙劣的英文翻译一些重要的部分,纯属娱乐,简单分享,保持学习,谨记谦虚. 如果你觉得这件事儿没意义翻译的又差,尽情的踩吧.如果你觉得值得鼓励,感谢留下你的赞,祝各位爱技术的园友在今后每一次应该猛烈突破的时候,不选择知难而退.在每一次应该独立思考的时候,不选择随波逐流,应该全力以赴的时候,不选择尽力

.Net中的异步编程

.Net中的异步编程? .net中实现异步有两种方式:第一种是多线程的方式,第二种是使用异步函数,其实在异步函数中使用的还是多线程的技术. 异步编程中比较关注也比较重要的技术点在于:1.当异步线程在工作完成时如何通知调用线程:2.当异步线程出现异常的时候该如何处理: 3.异步线程工作的进度如何实时的通知调用线程:4.如何在调用线程中取消正在工作的异步线程,并进行回滚操作. 虽然在.net中提供了众多的异步编程模式,但是推荐最好使用Task类,因为Task类使用线程池中的任务线程,又由线程池管理,

C#中的异步编程Async 和 Await

谈到C#中的异步编程,离不开Async和Await关键字 谈到异步编程,首先我们就要明白到底什么是异步编程. 平时我们的编程一般都是同步编程,所谓同步编程的意思,和我们平时说的同时做几件事情完全不同. 在计算机的世界里,同步编程的意思说 按照顺序来执行,或者说是 一个接着一个地有序的来执行, 比如目前我们在代码中有三件任务来执行,那么必须先执行完第1件,再执行第2件,接下来再执行第3件. 在这个过程中,第1件没有完成,你是没法开始做第2件事情的,必须等待. 比如一个人烧开水需要10分钟,5分钟找

全面解析C#中的异步编程

当我们处理一些长线的调用时,经常会导致界面停止响应或者IIS线程占用过多等问题,这个时候我们需要更多的是用异步编程来修正这些问题,但是通常都是说起来容易做起来难,诚然异步编程相对于同步编程来说,它是一种完全不同的编程思想,对于习惯了同步编程的开发者来说,在开发过程中难度更大,可控性不强是它的特点. 在.NET Framework5.0种,微软为我们系统了新的语言特性,让我们使用异步编程就像使用同步编程一样相近和简单,本文中将会解释以前版本的Framework中基于回调道德异步编程模型的一些限制以

Jquery中的异步编程浅析 延期(deferred)的承诺(promise)

引子 相信各位developers对js中的异步概念不会陌生,异步操作后的逻辑由回调函数来执行,回调函数(callback function)顾名思义就是"回头调用的函数",函数体事先已定义好,在未来的某个时候由某个事件触发调用,而这个时机,是程序本身无法控制的. 举几个常见例子: 事件绑定 动画 Ajax 上面的例子简单.典型,易于阅读和理解. 为了引出本文的主题,假设现在有3个ajax异步操作,分别为A.B.C,每个都封装成了函数,并可传入success回调作为参数. 请考虑以下场

promise 的基本概念 和如何解决js中的异步编程问题 对 promis 的 then all ctch 的分析 和 await async 的理解

* promise承诺 * 解决js中异步编程的问题 * * 异步-同步 * 阻塞-无阻塞 * * 同步和异步的区别? 异步;同步 指的是被请求者 解析:被请求者(该事情的处理者)在处理完事情的时候的通知机制. 异步:当事情处理完成后被请求者会发信息通知请求者该事情处理完成.在这期间被请求者可以选择是继续等待命令请求完成还是去做其他事等待被请求者返回. 同步:当事情处理完成后被请求者不会告知请求者,等到请求者发来询问是才会告知 阻塞:非阻塞 指的是请求者 阻塞:针对请求者来说的,委托其他人处理一

C#中的异步编程:await和async

根据代码示例来学习, 创建一个函数来模拟时间消耗的方法,此处为GetSomeThing函数. 要使用异步编程,需要使用一个async修饰的方法来包装调用GetSomeThing函数,此函数的返回值为Task类型,该类型表示进行并行运算的任务引用.此处示例为ConsumeManyTime函数. 现在就可以直接使用异步方式了,参考TestOne()函数中的代码,其实就是直接调用第二步中的函数ConsumeManyTime(); 总结:异步编程虽然示例三步,但是实际上就是两步的事情,第一步是模拟应用,

在JavaScript中实现异步编程模式的方法

本文总结了”异步模式”编程的4种方法,理解它们可以让你写出结构更合理.性能更出色.维护更方便的Javascript程序. 一.回调函数 这是异步编程最基本的方法. 假定有两个函数f1和f2,后者等待前者的执行结果. f1(); f2(); 如果f1是一个很耗时的任务,可以考虑改写f1,把f2写成f1的回调函数. function f1(callback){ setTimeout(function () { // f1的任务代码 callback(); }, 1000); } 执行代码就变成下面这

linux驱动程序中的异步编程

A 前面介绍的等待队列和轮询编程提供了较好的解决设备访问的机制,但是这些机制都 是由应用程序发起的,都需要应用程序主动访问设备.更完美的方式是由驱动程序主 动通知应用程序,也就是说,当驱动程序满足某些条件后,会主动通知应用程序处理 ,这些处理方式有些像面向对象编程的事件,而在linux内核使用的事件是接下来要介 绍的信号. #include<sys/types.h> #include<sys/stat.h> #include<stdio.h> #include<f