【问题标题】:Should this MVC4 API call be async?这个 MVC4 API 调用应该是异步的吗?
【发布时间】:2014-08-13 12:26:53
【问题描述】:

我刚刚继承了一个 MVC4 API 应用程序,它充当其他一些服务的基本请求转发器。应用的基本结构是这样的:

[请求] -> [rest api] -> [本地处理] -> [同步调用外部服务] -> [本地处理响应] -> [响应]

本地处理主要是关于验证内容并将其保存到数据库中。一点都不重。在极端情况下,外部请求可能需要 1 到 200 多秒的时间。 API 通常每小时处理数千个请求。

该应用托管在单个小型 Azure 云实例上。

我对线程感到困惑。从 API 处理程序方法到外部调用外部服务的整个过程都设置为异步:

public async Task<CustomResponseType> Post([FromBody] inputType) {
   // some validation
   ...
   // save some stuff to a db
   ...

   var response = await httpClient.PostAsync(someRequest)
   return await response.Content.ReadAsStringAsync();
}

首先,让这个过程异步有什么好处?据我了解,IIS 应该为传入的请求管理自己的线程池,并且由于我们正在等待对单个外部服务调用的同步响应,因此看起来实际上并没有并行处理任何事情。乍一看,异步服务请求似乎会争夺 IIS 将使用的相同资源,而且实际上同步执行所有操作可能更有效。

我已阅读此内容:http://msdn.microsoft.com/en-us/library/ee728598%28v=vs.98%29.aspx?wa=wsignin1.0 并了解在 IIS 端可能存在所谓的“线程饥饿”。那篇文章支持异步在这种情况下可能会有所帮助的想法,但我很难理解为什么。只增加 IIS 线程的数量会更容易吗?他们不是都在争夺相同的资源吗? IIS线程使用较重的问题是什么?

【问题讨论】:

  • 最后我看到了服务器上异步的一个很好的例子。我在 Stack Overflow 上看到的大多数异步 IO 情况根本没有好处。很高兴你质疑这个设计决定!

标签: c# .net asp.net-mvc-4 asynchronous async-await


【解决方案1】:

使用async/await 并不意味着“生成新线程来进行这些异步调用”。

Eric Lippert 在他严肃的Asynchronous Programming in C# 5 中精美地描述了使用async/await 的过程:

方法上的“async”修饰符并不意味着“这个方法是 自动安排在工作线程上异步运行”。它 意思是相反的;它的意思是“这个方法包含控制 涉及等待异步操作的流程,因此将 由编译器重写为继续传递样式以确保 异步操作可以在右边恢复这个方法 点。” 异步方法的全部意义在于你留在 当前线程尽可能。它们就像协程:异步 方法为 C# 带来了单线程协作多任务。

当您在方法上使用async 修饰符时,编译器将其推断为一个符号,并根据您的方法流程和其中使用await 关键字生成状态机。它不使用额外的 ThreadPool 线程,相反,当您在 I/O 绑定异步方法上 await 时,编译器会将控制权交还给调用者,在 ASP 的情况下.NET 将让当前线程返回到 ASP.NET ThreadPool 以用于其他传入的请求。一旦 I/O 工作完成,将通过同样由 ThreadPool 分配的 IOCP 线程调用延续,这意味着您实际上是在让线程做更多事情,而根本不创建新线程。

有很多很棒的帖子描述了这种效果:

  1. There Is No Thread - By Stephan Cleary
  2. Does async and await increase performance of an ASP.Net application
  3. ASP.NET async/await part 2
  4. The Magic of using Asynchronous Methods in ASP.NET 4.5 plus an important gotcha (By Scott Hanselman)
  5. Why use async requests instead of using a larger threadpool?

【讨论】:

  • “不使用任何额外的 ThreadPool 线程”... 除非这样做。 System.Net.Http.HttpClient 使用的 HttpClientHandler 在您发送请求时调用 Task.Factory.StartNew。这使用 ThreadPool 线程。虽然这是一个不应该做的例子,但 OP 的问题是指 HttpClient。
  • @bart - 这不是 HttpClient 特有的。我知道它在内部使用了一个额外的线程,你可以看到我的问题正是here。这是一个一般准则。
【解决方案2】:

由于外部调用最多可能需要 200 秒,所以是的,这似乎是对异步控制器的完全合适的使用。

当您有大量非 CPU 绑定的阻塞请求时,最好使用异步控制器 - 正如您现在所拥有的那样,I/O 就是一个很好的例子。标准的“同步”方法会在外部服务调用期间阻塞线程。想象一下,您有 10 个 IIS 工作线程(我知道这是一个人为降低的数字),并且或多或少地同时接收 10 个服务调用。由于某种原因(可能由于传递的参数),每个服务调用需要 100 秒。由于每个线程都在等待来自外部服务的响应,因此您的线程池已被耗尽——IIS 无法处理任何额外的客户端。

另一方面,使用异步调用,.NET 框架将请求“搁置”到外部服务,将其委托给系统进程。然后释放线程以处理其他客户端。一旦请求返回,该特定线程上的执行流将返回到异步方法。如果您熟悉 Javascript,您可以将await 之后的所有内容想象为回调方法。

这对于简单地增加线程数量是非常有利的,因为在等待之后返回执行流时,异步方法不需要昂贵的上下文切换。总而言之,async 提供了一种更好的方式来处理并发 I/O-bound 操作(注意:在处理 CPU 密集型并发进程时,async 失去了一些优势)。

这篇文章可能会有所帮助:http://blog.stevensanderson.com/2010/01/25/measuring-the-performance-of-asynchronous-controllers/

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-26
    • 2014-03-20
    相关资源
    最近更新 更多