【问题标题】:Should all my actions using IO be async?我使用 IO 的所有操作都应该是异步的吗?
【发布时间】:2014-04-22 18:07:22
【问题描述】:

当我阅读 MSDN 文章 Using Asynchronous Methods in ASP.NET MVC 4 时,我得出的结论是,我应该始终对 I/O 绑定操作使用异步等待。

考虑以下代码,其中 movieManager 公开了 ORM 的异步方法,例如实体框架。

public class MovieController : Controller
{
    // fields and constructors

    public async Task<ActionResult> Index()
    {
        var movies = await movieManager.listAsync();

        return View(movies);
    }

    public async Task<ActionResult> Details(int id)
    {
        var movie = await movieManager.FindAsync(id);

        return View(movie);
    }
}
  1. 这总是会给我更好的可扩展性和/或性能吗?
    • 如何测量这个?
  2. 为什么不在“现实世界”中使用它?
  3. 上下文同步怎么样?
    • 有那么糟糕,我不应该在 ASP.NET MVC 中使用异步 I/O 吗?

我知道这些问题很多,但是关于这个主题的文献有相互矛盾的结论。有人说您应该始终对依赖 I/O 的任务使用异步,其他人则说您根本不应该在 ASP.NET 应用程序中使用异步。

【问题讨论】:

  • 这可能是一种广泛的方式。 1 - 可能,但除非您确实存在可伸缩性问题,否则您不会看到更改;如果在 SO 之外有巨大的主题,则测量性能... 2 - 任何数字/参考? 3 - 任何参考/解释?

标签: c# io task-parallel-library async-await asp.net-mvc-5


【解决方案1】:

这总是会给我更好的可扩展性和/或性能吗?

可能。如果您只有一个数据库服务器作为后端,那么您的数据库可能是您的可扩展性瓶颈,在这种情况下,扩展您的 Web 服务器不会对整个服务的更广泛范围产生任何影响。

我该如何衡量?

带有负载测试。如果你想要一个简单的概念验证,可以查看this gist of mine

为什么在“现实世界”中很少使用它?

是的。 .NET 4.5 之前的异步请求处理程序编写起来非常痛苦,许多公司只是在这个问题上投入了更多的硬件。现在 .NET 4.5 和 async/await 势头强劲,异步请求处理将继续变得更加普遍。

上下文同步怎么样?

它由 ASP.NET 为您处理。我的博客上有一个async intro,它解释了当您await 一个任务时await 将如何捕获当前的SynchronizationContext。在本例中,AspNetSynchronizationContext 代表请求,因此 HttpContext.Current、文化等内容都会自动保留在 await 点上。

有那么糟糕,我不应该在 ASP.NET MVC 中使用异步 I/O 吗?

作为一般规则,如果您使用的是 .NET 4.5,则应使用 async 处理任何需要 I/O 的请求。如果请求很简单(即没有访问数据库或调用其他服务),则保持同步。

【讨论】:

  • 如果我是正确的:等待数据库调用将导致线程使用量减少,但在大多数情况下,这不会给您带来任何好处,因为您的数据库将成为瓶颈而不是阻塞线程。因此,除非您使用许多数据库或在 ASP.NET 应用程序中执行 CPU 密集型工作,否则异步 I/O 将无济于事。
  • 异步对 ASP.NET 上的 CPU 工作没有帮助;它实际上只对 I/O 有用。我不确定我是否同意“大多数情况”一词。 10 年前,大多数网站都由单个 SQL 服务器支持;但如今,越来越多的网站由 Web API、NoSQL 和 SQL Server Azure 提供支持,所有这些都可以扩展以匹配。
【解决方案2】:

这总是会给我更好的可扩展性和/或性能吗?

您自己回答了,您需要衡量并找出答案。通常,由于增加了复杂性,异步是稍后添加的东西,这是代码库中的第一大问题,直到您遇到特定问题。

我该如何衡量?

两种方式构建,看看哪个更快(最好用于大量操作)

为什么在“现实世界”中很少使用它?

因为复杂性是软件开发中最大的问题。如果代码很复杂,则更容易出错且更难调试。此外,更难修复的错误并不是潜在性能优势的良好权衡。

上下文同步怎么样?

我假设您的意思是 ASP.NET 上下文,如果是这样,您不应该进行任何同步,请确保只有一个线程正在访问您的上下文并通过它进行通信。

有那么糟糕,我不应该在 ASP.NET MVC 中使用异步 I/O 吗?

引入异步只是为了处理同步是一种损失,除非你真的需要性能。

【讨论】:

  • Typically async is something to add later on due to adding complexity - 不再。使用 .NET 4.5,该模式非常简单,应该在 Web 请求等待 IO 的任何地方使用它。
【解决方案3】:

将异步代码放入网站有很多负面影响:

  • 当数据片段之间存在依赖关系时,您会遇到麻烦,因为您无法将其设为异步。
  • 异步工作通常用于 API 请求之类的事情。您是否考虑过您不应该在网页中执行这些操作?如果外部服务出现故障,您的网站也会出现故障。这无法扩展。
  • 在某些情况下异步执行操作可能会加快您的网站速度,但您基本上是在引入麻烦。您总是最终等待最慢的一个,并且由于有时资源会因任何原因而变慢,这意味着某些东西减慢您的站点的风险会增加等于您正在使用的异步作业数量的因子。您必须引入超时来处理这些问题,然后是错误处理代码等。
  • 当由于 CPU 负载过重而扩展到多个 Web 服务器时,异步工作会伤害您。您过去在异步代码中放入的所有内容现在都会在用户单击链接的那一刻同时触发,然后放慢。这不仅适用于 CPU 负载,还适用于数据库负载甚至 API 请求。您将看到所有系统资源的使用模式非常糟糕:大量使用高峰,然后再次下降。这不能很好地扩展。同步代码没有这个问题:作业只有在另一个完成后才开始。

网站的异步工作是一个陷阱:不要去那里!

将繁重的代码放入工作人员(或 cron 工作)中,在用户要求之前完成这些事情。您可以将它们保存在数据库中,并且可以继续向您的网站添加功能,而不必担心触发太多异步作业等等。

网站的性能被严重高估了。当然,如果您的页面在 50 毫秒内呈现,那很好,但如果需要 250 毫秒,人们真的不会注意到(测试这一点:在您的代码中添加 Sleep(200))。

如果您只是将工作转移到另一个进程并使网站成为仅与您的数据库连接的接口,那么您的代码将变得更具可扩展性。不要让你的网络服务器做它不应该做的繁重工作,它不会扩展。您可以让一百台机器在每个网页上总共花费 1 个 CPU 小时 - 但至少它可以扩展为页面仍然在 200 毫秒内加载的方式。祝你好运,使用异步代码实现这一目标。


我想在这里添加一个旁注。虽然我对异步代码的看法似乎很强烈,但它主要是对程序员的看法。异步代码很棒,可以产生性能差异,证明我列出的所有要点都是错误的。但是,它需要对代码进行大量微调以避免我在这篇文章中提到的要点,而大多数程序员无法处理。

【讨论】:

  • 异步在 .NET 4.5 中非常简单,var result = await FooAsync() 是您进行异步操作所需的全部。如果您的团队一开始就无法使用异步操作,则不应使用异步操作。我同意某些 I/O 不适合网页,但数据库也被认为是 I/O。如果异步代码易于编写、测试和维护,我为什么不对使用数据库的页面使用异步操作?
  • @annemartijn:出于我在回答中提到的原因。并行代码无法处理数据依赖关系,当为外部服务完成时,这是一个坏习惯,你总是在等待最慢的工作完成,并且它不能跨多个盒子扩展。随着您的网站获得更多功能,您会发现您的数据集中会有越来越多的依赖项,此时您会希望自己刚刚使用了后台工作程序。
  • @annemartijn:仅仅因为某些东西很容易实现并不意味着它是一个好主意。您可以使用完全同步的代码保持您的网站快速。如果没有,您可能做错了什么 - 使代码异步不是解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-16
  • 1970-01-01
  • 2017-03-26
  • 1970-01-01
  • 2020-03-05
相关资源
最近更新 更多