这是一个非常好的问题,理解它是理解异步IO为何如此重要的关键。之所以将新的异步/等待功能添加到C#5.0中,是为了简化异步代码的编写。对服务器上异步处理的支持并不是新的,但是自ASP.NET 2.0开始就存在。
就像Steve向您展示的那样,通过同步处理,ASP.NET(和WCF)中的每个请求都从线程池中获取一个线程。他演示的问题是一个众所周知的问题,称为“线程池不足”。如果在服务器上进行同步IO,则线程池线程将在IO期间保持阻塞状态(不执行任何操作)。由于线程池中的线程数有限制,在负载下,这可能导致所有线程池线程都被阻塞,等待IO,并且请求开始排队,从而增加了响应时间。由于所有线程都在等待IO完成,因此您将看到CPU占用率接近0%(即使响应时间通过了屋顶)。
您在问什么(为什么我们不能只使用更大的线程池?)是一个很好的问题。实际上,到目前为止,这就是大多数人解决线程池饥饿问题的方式:线程池上只有更多线程。Microsoft的一些文档甚至指出,此方法可以解决线程池不足的情况。这是可以接受的解决方案,并且在C#5.0之前,这样做比将代码重写为完全异步要容易得多。
但是,该方法存在一些问题:
没有在所有情况下都能发挥作用的值:您将需要的线程池线程数线性地取决于IO的持续时间以及服务器的负载。不幸的是,IO延迟几乎是不可预测的。这是一个示例:假设您向ASP.NET应用程序中的第三方Web服务发出HTTP请求,这大约需要2秒钟才能完成。您遇到线程池不足的情况,因此决定将线程池的大小增加到200个线程,然后再次开始正常工作。问题是,也许下周Web服务将出现技术问题,从而将其响应时间增加到10秒。突然,线程池的饥饿又回来了,因为线程被阻塞的时间延长了5倍,所以现在您需要将线程数量增加5倍,达到1,000个线程。
可扩展性和性能:第二个问题是,如果这样做,每个请求仍将使用一个线程。线程是一种昂贵的资源。.NET中的每个托管线程都需要为堆栈分配1 MB的内存。对于持续5秒且每秒负载500个请求的网页,您的线程池中将需要2500个线程,这意味着2.5 GB的内存用于不做任何事情的线程堆栈。然后,您会遇到上下文切换的问题,这将严重损害计算机的性能(影响计算机上的所有服务,而不仅仅是Web应用程序)。即使Windows在忽略等待的线程方面做得相当不错,但它并不是设计来处理如此多的线程。
因此,增加线程池的大小是一种解决方案,而且人们已经这样做了十年(即使在Microsoft自己的产品中),在内存和CPU使用率方面它的可扩展性和效率也较低,而且您始终处于IO延迟突然增加会导致饥饿的摆布。直到C#5.0为止,异步代码的复杂性对于许多人来说都是不值得的麻烦。async / await像现在一样改变了一切,您可以从异步IO的可伸缩性中受益,并同时编写简单的代码。
更多详细信息:http : //msdn.microsoft.com/zh-cn/library/ff647787.aspx “当Web服务调用继续进行时,如果有机会执行其他并行处理,请使用异步调用来调用Web服务或远程对象。如果可能的话,应避免同步(阻塞),因为传出调用Web服务通过使用线程从ASP.NET线程池作出调用Web服务。阻塞调用减少可用线程数,用于处理其他传入的请求。 “