我如何看待异步代码?!

莱斯利·理查森

异步代码对于编写响应性应用程序越来越重要,它越来越流行。不幸的是,异步编程增加了代码的复杂性。因此,无论您的经验水平如何,都很难理解这些代码是如何工作的。无论您是新手还是只想复习一下,这里都是异步代码世界的简介!

 

什么是异步代码?

异步(async)编程允许您在不停止(或舞台调度)执行操作的整个线程。关于异步代码的一个常见误解是它提高了性能,但这并不一定是真的。相反,异步编程的主要优点是它增加了任务的数量(吞吐量)可以并发执行,而不必阻塞执行这些操作的线程。

您可能认为异步代码有点像多线程代码。毕竟,在这两者中可以同时执行许多方法。实际上,异步编程可以与单一或多线程应用程序一起使用。这意味着您可以使用单线程异步程序,其中一个线程可以运行并发任务。相反,您也可以有一个多线程异步应用程序,其中多个线程可以各自运行多个并发任务。

 

为什么我应该使用异步代码?请举例说明!

要使用类比来演示异步编程,请考虑烘焙蛋糕的操作。此操作将由执行多个步骤(或任务),如下面的代码所示。这段代码是可用的,一旦方法执行完毕,您仍然会有一块美味的蛋糕。然而,由于所有代码都是同步,每行都将按顺序运行。换言之,您将在等待烤箱完成预热时完全静止。你可以同时为你的蛋糕做面糊!

同步MakeCake()函数
同步MakeCake()函数

 

同步蛋糕程序
同步蛋糕程序

 

在现实生活中,你通常希望在烤箱预热时制作面糊,或在蛋糕烘焙时制作糖霜,从而实现多重任务。这样做可以提高你的工作效率,让你烤蛋糕的速度更快。这就是异步代码派上用场的地方!通过使现有代码异步,我们可以执行更多操作来打发时间等待任务比如在烤箱里烤蛋糕。为此,我们修改了代码以包含一个名为通过时间。此代码保存任务的状态,开始运行另一个同步或异步函数,并在实际需要时检索保存的任务的值。

异步MakeCake()函数
异步MakeCake()函数

 

映像AsyncCake运行
异步蛋糕程序

 

与缺少密码时间函数调用时,MakeCakeAsync可以在不阻塞线程的情况下完成更多任务,并缩短执行整个方法所需的时间。

异步与同步程序比较
异步与同步程序比较

 

如何在中编写异步代码。净利润?

谢天谢地,C#让编写异步代码变得“小菜一碟”任务 类型和等待 异步关键字。  The任务类型告诉调用方最终的返回值类型。它还指示在处理调用方方法时可以执行其他操作。  The异步关键字启用等待关键字,它让编译器知道我们需要函数的返回值,但不是马上。因此,我们不需要阻塞调用,可以继续运行其他任务,直到需要等待的值。异步方法最初将同步运行,直到它遇到等待关键字。此时将开始异步执行。

 

我刚刚了解了异步代码!现在怎么办?

虽然使用异步代码烤蛋糕很好,但还有很多其他实际应用程序可以使用异步代码。两个最常见的示例包括:

  • 使用HTTP请求的程序–根据请求,HTTP调用可能需要很长时间才能完全处理。使用异步代码可以让您在等待服务器响应的同时执行其他工作。

HTTP GET请求示例
HTTP GET请求示例

 

  • 使用UI元素的程序–WPF应用程序或任何使用按钮、文本框和其他UX资产的应用程序都很适合异步实现。例如,WPF应用程序接收要解析的文件可能需要一段时间。通过使此操作异步,您仍然可以与UI交互,而无需在等待功能完成时完全冻结应用程序。

现在您已经了解了异步编程的基础知识,是时候改进它了!在编写稳定的异步代码时,有很多秘密的注意事项。有关探索这些提示和技巧的优秀资源,请查看David Fowler关于异步编程的帖子

和所有代码一样,将来需要诊断异步程序中的错误。若要了解如何在Visual Studio中调试异步代码,请收看即将发布的博客文章…

旁注:不,我没有用蛋糕来比喻,只是因为我最近做了很多烘焙。😉

红色天鹅绒纸杯蛋糕

20条评论

讨论结束。登录以编辑/删除现有评论。

  • Ion Sorin Torjo公司 0

    你好,莱斯利,

    是的,C#确实让异步的使用变得异常简单。

    话虽如此,但您是VS团队的一员,请您告诉我们以下内容何时可用:

    *加快UWP编译速度——构建非平凡的UWP应用程序简直是疯了。编译时间快到了极点!MS已经知道这一点多年了。
    *能够在UWP中查看异步堆栈跟踪。失去初始上下文会使调试UWP应用程序变得非常困难。

    谢谢!

    最佳,
    约翰

  • 尼尔·麦克马伦 0

    嗨,莱斯利,这是一篇很好的文章,也是一个很好的异步“现实世界”示例!我认为围绕“PassTheTime”解释的措辞有点令人困惑(至少对我来说是这样!),因为它暗示“PassThe Time”处理的是异步和状态恢复,而不是“等待”–您有点跳过了异步方法运行直到遇到阻塞操作的关键点,因此通过使操作异步,您实际上将语义更改为“StartBakingTheCake”等(如果你已经知道这一点,那么很明显,但你的文章似乎是针对那些可能需要一些时间才能理解这一点的初学者。)

    顺便说一下,有一个非常好的库,叫做TaskTupleAwaiter(https://github.com/buvinghausen/TaskTupleAwaiter/blob/master/README.md)这为这种“只需并行运行一些任意的异步操作”引入了一个很好的语法。在我看来,它所允许的视觉呈现。。

    等待(PrepareIngredientsAsync、PreheadOvenAsync);

    与将它们放在连续的行中相比,这些事情是一起完成的,这一点更加明显。

  • 里克·莱奇 0

    我喜欢莱斯利的文章。我一直很好奇…MSDN上的大多数代码示例都会立即等待每一项任务。即使是现成的新项目VisualStudio模板也会立即等待每一项任务。这与同步编程不一样吗?为什么在这些情况下使用异步关键字?为了实现异步行为,我一直在做您在这里所做的事情。我将任务分配给一个变量,稍后等待它。我期待着您的评论。

    • 加文 1

      当您等待任务时,取决于您何时需要结果,但由于典型的情况是我们进行异步调用并希望立即使用结果,因此只需编写就更简单、更清晰了var someResult=等待DoWorkAsync();

      异步真正的魔力在于与同步代码类似,但当您调用DoWorkAsync()方法返回一个任务而不仅仅是返回同步结果。

      如果任务不是完整的当执行达到等待然后,线程将返回线程池,并可以自由地执行其他工作,而不是像在同步代码中那样执行线程阻塞DoWorkAsync()完成后,自由线程将在等待

      所以哪里你把等待不会使代码同步。你只需在想要结果的时候等待任务。对于不返回结果的任务,您可以将结果视为“任务已完成”,通常是因为其副作用。

      这就是为什么异步/等待模式主要适用于IO调用,否则会被阻塞的线程可以在IO所用的几毫秒内执行有用的工作,这意味着我们可以用一个或几个线程为数千个IO调用提供服务。

  • 巴勃罗 0

    非常好的帖子!我需要经常这样做,你的帖子激励我这样做。谢谢。

  • Алексей Кайгородов 1

    有两种并行活动:线程和任务。异步程序是一种使用任务(很可能还有线程)的并行程序。任何并行程序都可以用两种方式实现。任务的优点是内存消耗更少,因为它们没有附加堆栈。分解是更复杂的编程:单个线程的工作通常需要多个任务。此外,程序员不得允许他们的任务执行阻塞操作。

    • 罗伯特·海兰德 0

      感谢您简洁的定义描述了这两种范式之间的差异。

  • 杰伊 0

    我可以在哪里下载代码来创建你博客中显示的应用程序?我只想开始,所以最好的事情是看看这个代码是如何创建的。

    希望与并行处理进行比较,但您的代码看起来非常高效。

    谢谢。

  • 埃里克·邦格斯 0

    “异步方法最初将同步运行,直到遇到await关键字。”
    听起来好像运行了第一个PreheatTask,然后添加了成分,然后异步检索预热结果。

    事实上(我认为)相反的情况发生了:主功能和预热功能以异步方式运行,直到AWAIT强制它们再次同步。

    (我想我在什么地方读到过编译器将异步函数拆分为初始同步块……但这与本简介无关,并导致混淆)

  • 文森特·索恩 1

    异步代码,尽管到处都在大肆宣传,但并不是到处都可以使用的!

    首先,并不是所有你需要“快速负责”的地方。如果你想要HTML页面,你需要点击半空的页面吗??首先等待它加载,然后进行交互。许多操作都是一样的:当你保存一个文档时,等待——确保它是安全的,然后继续工作。尤其是当你使用慢速媒体时。特别是当Windows在简单操作上表现得像慢镜头时(因为肮脏的架构)。

    其次,异步代码使代码更复杂,更难调试。阅读:“使代码容易出错”。我宁愿为可靠的代码而不是“快速编写”付费。微软的想法不同,这就是为什么我们拥有Win10的“cr**ppy质量”。

    停止炒作,我们都已经知道什么是异步。不要再推了,让我们自己决定什么时候需要这个功能。

    • 迈克 1

      文森特,你说得对。“从众心理”使编码人员相信异步编码是某种革命。即使是上面给出的例子(烤蛋糕)在技术上也是不正确的。其他网站上人们给出的比萨饼制作示例也不正确。这就是为什么……

      当你打开烤箱,继续做其他事情(比如,做糖霜);一定有人或什么东西在加热烤箱。是的,现实生活中的烤箱是一个设备,但内部正在发生一些事情来加热它——电力消耗、风扇移动、加热元件等。在真正的编码术语中,这是一个–进程–在共享或私有空间中执行的一段代码(线程也是一个进程,在其他一些进程中共享空间)。

      同样,它的意思是,“加热炉”功能将跨另一个进程/线程执行,它可能不是您的进程,但它是一个单独的进程。因此,从技术上说,异步编码可以在一个线程上发生是不正确的。如果您真的只有一个线程,那么在任何给定的时间都只会运行一个异步函数,就像不使用异步字(默认同步代码)一样。

      因为现在的CPU通常有多个CPU(核心),是的,您可以同时运行多个进程。但您的操作系统也会对CPU进行时间切片,以给人留下这样的印象:它正在运行更多的进程(实际上,一个执行CPU-element在任何时候都只能运行一个进程)。

      我觉得异步编码被添加到了C#中,因为Windows进程会消耗太多的内存/资源来启动(与Linux相比)。因此,为了稍微加快速度,为什么不在需要之前启动一组进程(线程),然后根据需要分配它们(线程池)。这就像雇佣一些空闲的员工,即使你不需要他们,是的,你仍然需要支付他们的工资,当然,线程池也会有开销。不断地要求员工完成不同的任务将是更大的开销。如果你被要求不断放弃你正在做的事情,接受一项新任务,你会有什么感觉。为不同的任务(函数)不断重新使用相同的线程,也应该有相同的开销来始终保存进程状态。

      在这种情况下,当您异步执行一个函数(比如F1)并开始执行其他操作时,函数F1将“休眠/暂停”或必须在其他线程上运行(可能是从池中)。它并没有真正解决任何问题。

      事实上,普通(你可以称之为同步)编码并没有真正的问题。

      Asyn编码可能会使您的编码过于复杂;花费更多的时间和金钱,并在代码中添加一些漂亮的bug!

      记住:解决问题最简单的办法,几乎总是最好的办法。但在现实的编码世界中,Async并没有解决任何问题。

      我错了吗?如果是,请提供明确的理由,谢谢。

      另一方面,Async背后真正的“逻辑”是在电子产品中;在这种情况下,它是有意义的(你可以在互联网上发现,怎么做)。

      由于上述原因,异步javascript也很糟糕。你喜欢网页以点点滴滴的方式改变形状、调整大小、移动和加载的网站吗?我一点也不喜欢它们。以烘焙蛋糕为例,我宁愿等待并得到整个蛋糕,而不是先得到糖霜,然后是浇头,然后是底部!这就是异步在网站页面上的样子。问任何人,他们都会告诉你,他们喜欢那些静止不动的页面,在你阅读区域时,以及当整个页面一次性显示时,即使他们必须等待,也不要改变/移动。

  • 安德斯·鲍曼 1

    你好,莱斯利。
    谢谢你的一篇有趣的文章。好的例子总是能增进理解。对于异步,我认为一个比烤蛋糕更好的例子是餐厅有多个服务员和厨师。在同步解决方案中,服务员向厨师点餐。然后,服务员在厨房等待食物烹饪。他在等的时候不能招待其他客人。在异步解决方案中,服务员带着订单去厨房,然后立即返回餐桌接受其他订单。菜做好后,厨师按门铃,服务员拿起菜递给客人。

    谢谢,
    安德斯

  • 尼克 0

    做得好!这也许是我读过的关于异步编码的最简洁的文章/例子。说明异步/等待的一个简单而直接的好处是非常明智地利用读者的时间。谢谢,我们将与其他开发人员分享这篇文章。

    最佳,

    尼克

  • 萨姆·史密斯 0

    很棒,易于理解的例子。也谢谢你的源代码!

  • 金·霍曼 0

    谢谢您!

  • 杰西·迪基 0

    等等,你是说仅仅从异步方法调用一个异步方法,实际上会导致最后一个方法在那一点开始异步运行,即使你实际上直到稍后才等待它?这是代码中与其他示例不同的部分,通常等待与方法调用出现在同一行,而不是将返回值存储在Task变量中。您确定Task实际上正在运行吗之前打电话等一下?如果你不等待一切呢?如果你只是想让它在任何时候运行并完成,而没有你需要的结果,可以吗?

  • 约翰·阿克马尼斯 0

    你好,莱斯利,

    出色的帖子和一个简单易懂的示例,说明Async如何使用蛋糕类比工作。我遇到的最好的例子。

    干杯

    约翰

  • 罗伯·肯尼迪 0

    我想感谢您的文章,但更重要的是,感谢您的示例代码没有到处都是意大利面返回。一个出口是一件美丽的事情。🙂

  • 穆鲁格桑·马诺卡兰 0

    做得很好(烤…多层彩色蛋糕)!!!

    我们能为这些博客提供评级并在顶部提供搜索选项吗(高级搜索…按作者、日期、技术等)?也许我不知道该怎么做??!!

反馈usabilla图标