异步编程系列第02章 你有什么理由使用Async异步编程

时间:2022-07-25 08:20:44

p {
display: block;
margin: 3px 0 0 0;
}
-->

写在前面

在学异步,有位园友推荐了《async in C#5.0》,没找到中文版,恰巧也想提高下英文,用我拙劣的英文翻译一些重要的部分,纯属娱乐,简单分享,保持学习,谨记谦虚。

如果你觉得这件事儿没意义翻译的又差,尽情的踩吧。如果你觉得值得鼓励,感谢留下你的赞,愿爱技术的园友们在今后每一次应该猛烈突破的时候,不选择知难而退。在每一次应该独立思考的时候,不选择随波逐流,应该全力以赴的时候,不选择尽力而为,不辜负每一秒存在的意义。

转载和爬虫请注明原文链接http://www.cnblogs.com/tdws/p/5618321.html,博客园 蜗牛 2016年6月25日6:15pm。

目录
为什么使用异步编程

异步编程很重要很有用处,原因有很多,主要看你在构建什么类型的应用程序。其中一部分的用处和益处在任何应用程序中都随处可见,即使某些类型的应用程序你没有接触过。如果这适合你,请阅读整篇文章,做为背景知识将会帮助你更好的理解整个上下文。

桌面应用程序

桌面app有一个主要的性能要求。要求app让用户一直感觉到是可响应的。HCI研究表明,缓慢的应用程序不会得到用户的关注,最好是要有一个进度条提示器。

当应用程序冻住或者说未响应状态,用户会变得沮丧。冻结的原因通常是因为一个耗时操作,或者因为一个缓慢的计算,或者因为IO,或者网络请求。

你用的C#的桌面UI框架都是单个UI线程的,包括:

·Winforms

·WPF

·Silverlight

UI线程是唯一可以控定特性窗口的,也是唯一一个线程来检查用户输入和为他们执行相应工作。如果这个线程很繁忙或者被阻塞数几十毫秒,用户就会注意到这个app是缓慢的。

异步代码,甚至手工编写,意味着UI线程可以返回检查消息队列进行对用户操作的主要工作,并对他们响应。它也可以执行进度动画,并在最近版本的窗口,鼠标悬停动画,这都是重要的视觉线索给用户,给人一个好印象的响应的应用程序。

所有常见的用户界面框架只使用一个线程的原因是为了简化同步。如果有很多线程的话,当一个线程正在铺设布局控件过程中,另一个线程试图读取按钮的宽度,这样冲突了。为了避免这样的事情发生,你需要大量的使用锁,这将会大大降低程序的性能。

打个比方:咖啡馆

我想用一个比喻来直观的帮你把握所涉及的问题。如果你觉得已经理解,那么请跳过这一小节。

想象一下,有个小咖啡馆为顾客早餐提供面包,唯一的工作人员就是老板,他非常注重和关心客户服务,但是还没有学会异步的技术。UI线程模型和咖啡店的老板很相似,就像在计算机中必须通过线程一样,咖啡店员工在咖啡馆中工作,在这样的情况下,就像他们只有一个UI线程一样。

第一个客户要一片面包,老板将面包放到烤箱中,然后他在烤面包的过程中等待着。客户问老板在哪能找到黄油,但是老板把她忽略了,就像他是阻塞型代码一样。五分钟过去,面包已经烤好并且拿给了客户。到了这个时候,已经排起了长队,客户最讨厌的就是等待和被忽略,老板这样做一点也不理想。

现在,我们来教老板异步。

首先要确保他的烤箱可以异步操作,当我们编写异步代码时,我们要确保耗时操作在执行结束时能够告诉我们,同样的,面包烤箱也需要一个定时器,并且要响亮以便能够被他注意到。

另外就是他能够在烤箱开始的时候忽略它,他应该回去继续为顾客服务,同样的,我们的异步代码也要在执行耗时操做时返回,这样UI线程可以继续相应用户操作。

有两点原因:

·可以更好的响应用户请求—客户可以询问黄油在哪并且不被老板忽略。

·用户可以同时开始另一个操作—下一个客户也可以开始点餐并且让老板烹饪。

咖啡馆老板现在可以同时为多个客户服务,唯一的限制就是烤箱的数量和拿取面包的时间。但这样带来了一系列问题:老板发现自己并不能记住哪片面包是哪个客户所点,UI线程完全不能记住当他返回时,他在等待的是哪个操作。

所以我们必须再开始的时候附加一个回调,去提醒我们当他结束时该做什么。至于咖啡馆老板,很简单的就是在烤箱上贴上客户姓名的标签。但我们可能需要更复杂的东西,一般来说我们希望能够对耗时工作结束后需要做什么事情,提供完整说明,一旦完成了工作。

有了这些,咖啡馆老板现在完全是异步的了,并且生意红火。用户体验感变得更好。这就是等待很少,更具效应能力。希望这个比方能够帮助你的直觉来理解为什么异步在UI application上如此重要。

Web应用程序服务端代码

ASP.NET的Web服务器没有像UI代码一个线程的的硬限制。这就是说,在web中使用多线程编程依然有很多好处。耗时操作,尤其是远程数据库Query,在web app中非常常见。

取决于你的IIS版本,将会有用于处理web请求的线程总数和并发请求数的限制。如果你的请求花了大量的时间在等待数据库Query上,那么增加并发请求数去增加服务器吞吐量是一个好的方法。

当一个线程被阻塞,一直在等待,它不占用UPU时间。然而,你不要误以为这意味着它不占用服务器资源。事实上,线程占用两项重要的开销,即使他们在被阻塞:

·内存

每个托管线程储备在Windows上的虚拟内存的字节。如果你有几十个线程是完全没有问题的,但是当你有上百个线程时很容易就会失控。如果内存使用了磁盘上的虚拟内存空间,那么你的线程会变得特别慢。

·调度器的开销

操作系统的调度器负责选择在什么时间,在哪个CPU上,执行哪个线程。即使当线程被阻塞时,调度器也必须考虑到他们,并且判断它们是否变得非阻塞(阻塞结束)。这样减缓了线程上下文切换,甚至可以拖慢整个系统。

在他们之间,这些开销负载到到你的服务器上,增加延迟并且降低吞吐量。

请记住:异步编程的主要特征是当线程开始执行一段耗时操作,这个线程会被释放去做其他一些事情。在ASP.NET代码下,线程来自于线程池,所以在执行耗时操作期间,线程会被返回到线程池,他可以处理其他请求,以很少的线程就可以来处理同阻塞代码情况相同的请求数量。//译者注解,如果你不理解这句话,可以参照我的另一篇文章中的解惑二:文章链接

另一个比方:餐馆厨房

web服务器和餐馆模型很接近,很多客人点餐,厨房会尽可能的快速满足他们的要求。

我们的厨房有很多厨师,每个厨师代表一个线程。他们按照用户的订单来烹饪,但是在整个准备过程中,每道菜只需要烹饪一会儿,并且厨师可能等待着没什么事情做。这反映了web请求处理的方式,通常在执行数据库Query请求数据库数据的这段时间,web服务器并没有参与。

在类似于阻塞型的厨房,厨师将会在烹饪工具前等待菜肴的烹饪。精确模拟一个线程,厨师有一个奇怪的合同,厨师在等待烹饪结束的过程中不被支付薪水,因为一个线程不占用CPU时间当他们被阻塞时,也需要他们在这期间读报纸。

但即使我们没有向他们支付,我们依然可以使用新厨师为另一道菜肴,那些等待烹饪结束的厨师依然空闲在厨房。但是,我们不能让几十个厨师在厨房工作,这样连转身都困难,最终导致每个人的工作效率都很低。

当然我们用异步工作方式会更好,每当菜肴正在烤箱中烹饪,厨师记下当前在烹饪的是什么菜肴,在什么阶段或者阶段,然后找到一项新的任务(菜肴)去做。当菜肴烹饪结束,任意一个厨师可以将菜肴拿出来继续处理。//译者注释:记住,每个厨师代表一个线程,试着当作线程来理解,你会明白原理。

Web服务器正是这种强大的系统。是需要极少部分线程就可以承受以前的并发量,或者能做到以前在开销上不可行的事情。事实上,一些web框架,特别是nodejs,拒绝多线程并行的方式,选择单个线程来异步处理所有请求。他们可以用单线程来处理比多线程并行更多的请求,但是阻塞,系统可以处理的总数。同样的,一个组织能力强的厨师在一个空厨房中可以烹饪比,上百个厨师在厨房还多的菜肴,因为出事多了他们花了几乎所有时间在绊倒彼此和阅读报纸上。

Silverlight, Windows Phone, and Windows 8

暂时不翻译此部分

并行代码

计算机是多处理核心的,每个核心之间相互独立。程序需要充分利用多核心的优势,但由这些程序使用的任何内存不能被并行代码立即同时写入,否则内存容易被损害。

也许在编程中使用比较纯净的统一的风格是更好的,即不在内存中操作状态,而是操作不变值。这将会让我们享受并行系统的好处,但这也不适用于某些程序。就像User Interfaces需要状态,数据库就是状态。//译者注释:原文的状态是state,这个词也许翻译为状态不恰当。

标准的解决方案是使用互斥锁以防并行代码同时访问相同内存。但这又带来一系列问题,你的代码通常会带一把锁,然后做出一个方法调用或注册一个事件又带另一个锁。通常,同时保持两个锁不是必要的,但代码是没有人类这样思想的。

下面是一个对于锁的假设结构,意思是说,总体来讲,更多的线程结束,等待锁,直到他们可以做一些有用的工作。在一些情况下,两个线程同时等待另一个线程保持的锁,引发了死锁。这些错误是很难预测,很难重现,而且往往很难修复。

最有前景的解决方案之一是Actor模型计算。这是一个在每块可写的内存只能存在于一个Actor内的设计。唯一的方式来使用这块内存是向Actor发送消息,从而一次处理一个,并且可能会得到另一条回复消息。这就是异步编程。询问Actor的操作是一种典型的异步操作,因为我们可以继续做其他事情直到回复消息抵达。这意味着你可以使用异步来做,详细将会见第10章.

一个示例

我们将会看到一个desktop UI app,它急需转换为异步风格。源代码地址https://bitbucket.org/alexdavies74/faviconbrowser .我建议你如果可以的话跟随着来进行,在VS中打开。

运行程序,你会看到一个窗口有一个按钮。如果你按这个按钮,它会显示一些流行的网站的图标。它通过下载大多数网站包含的一个文件名为favicon.ico(图2-1)。

************************配图
downloads the favicon and adds it to a WPF WrapPanel in the window.

让我们看一看代码。重要的方法是下载的图标,并将其添加到窗口中的WPF wrappanel。

private void AddAFavicon(string domain)
{
WebClient webClient = new WebClient();
byte[] bytes = webClient.DownloadData("http://" + domain + "/favicon.ico");
Image imageControl = MakeImageControl(bytes);
m_WrapPanel.Children.Add(imageControl);
}

你将会注意到代码但全是同步的,线程在下载的过程中是阻塞的。你可能还会注意到点击按钮的时候,窗口有几秒变成了未响应的状态。和你知道的一样,这是因为在下载小图标icons时UI线程阻塞,并且不能返回处理用户的操作。

我们将使用这个例子在接下来的章节,并将其转化为异步编程的程序。

写在最后

早上九点多开始,除了吃饭,健身,基本一直在翻译和学习第二章。我觉得对于ASP.NET异步编程,厨房这个这个比方简直太棒太恰当了!如果你对你有些许益处,不要吝啬你的赞,给个鼓励。不准确的地方,也请前辈们不吝赐教,我将虚心改正。

下一章 http://www.cnblogs.com/tdws/p/5628538.html