我刚刚继承了一个MVC4 API应用程序,它充当其他一些服务的基本请求转发器。该应用程序的基本结构是这样的:
[request] - > [rest api] - > [本地处理] - > [同步呼叫外部服务] - > [响应的本地处理] - > [反应]
本地处理主要是验证并将内容保存到数据库中。什么都没有重。在极端情况下,外部请求可能需要1到200秒以上的时间。 API通常每小时处理数千个请求。
该应用程序托管在一个小型的天蓝云实例上。
我感到困惑的是线程。从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线程使用的问题是否更重?
答案 0 :(得分:4)
使用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线程调用continuation,这意味着你实际上让线程做得更多,而根本没有创建新线程。
有很多很棒的帖子描述了这种效果:
答案 1 :(得分:3)
由于外部呼叫最多需要200秒,然后是,这似乎完全适合使用异步控制器。
当您拥有大量非CPU绑定阻塞请求时,最好使用异步控制器 - 正如您现在所做的那样,I / O就是一个完美的示例。标准的“同步”方法会在外部服务调用期间阻塞线程。想象一下,你有10个IIS工作线程(人为的数字,我知道),并且或多或少同时接收10个服务调用。由于某种原因(可能由于传递的参数),每次服务调用需要100秒。当每个线程都在等待来自外部服务的响应时,您的线程池已经饿死了 - IIS无法处理任何其他客户端。
另一方面,使用异步调用,.NET框架将请求“搁置”到外部服务,并将其委派给系统进程。然后释放该线程以处理其他客户端。请求返回后,该特定线程上的执行流将返回到异步方法。如果您熟悉Javascript,则可以将await
之后的所有内容想象为回调方法。
这非常有利于简单地增加线程数,因为async方法在await之后返回执行流时不需要昂贵的上下文切换。总而言之,async提供了一种处理并发I / O绑定操作的更好方法(注意:在处理CPU繁重的并发进程时,async失去了一些优势)。
本文可能有所帮助:http://blog.stevensanderson.com/2010/01/25/measuring-the-performance-of-asynchronous-controllers/