这是一个很好的问题,理解它是了解异步IO如此重要的关键。新的异步/等待功能添加到C#5.0的原因是为了简化编写异步代码。但是,支持服务器上的异步处理并不新鲜,它自ASP.NET 2.0以来就存在。
就像史蒂夫告诉你的,通过同步处理,ASP.NET(和WCF)中的每个请求都从线程池获取一个线程。他演示的问题是一个众所周知的问题,称为“线程池饥饿”。如果您在服务器上创建同步IO,则线程池线程将在IO持续时间内保持阻塞状态(无需执行任何操作)。由于线程池中的线程数量有限制,所以在负载下,这可能会导致所有线程池线程都被阻塞等待IO的情况,并且请求开始排队,导致响应时间增加。由于所有线程都在等待IO完成,所以您将看到CPU占用率接近0%(即使响应时间已经过去了)。
你在问什么(为什么我们不能只使用一个更大的线程池?)是一个很好的问题。事实上,这是大多数人直到现在一直在解决线程池饥饿问题的方式:线程池中只有更多的线程。微软的一些文档甚至指出,修复线程池可能发生饥饿的情况。这是一个可以接受的解决方案,在C#5.0之前,要比重写代码完全异步更容易。
没有与方法的几个问题,但:
有没有价值,在所有情况下的工作原理:您将需要线程池中的线程数量线性依赖于持续时间IO和服务器上的负载。不幸的是,IO延迟大多是不可预测的。这里是一个例子: 假设你在ASP.NET应用程序中向第三方Web服务发出HTTP请求,这需要大约2秒的时间才能完成。你会遇到线程池饿死的情况,所以你决定增加线程池的大小,比如说200个线程,然后它再次开始工作。问题是,也许下周,网络服务将出现技术问题,将响应时间提高到10秒。突然之间,线程池饥饿又回来了,因为线程被阻塞了5倍,所以你现在需要增加5次,到1,000线程。
可扩展性和性能:第二个问题是,如果你这样做,你仍然会为每个请求使用一个线程。线程是一个昂贵的资源。 .NET中的每个托管线程都需要为该堆栈分配1 MB的内存。对于持续5秒制作IO的网页,每秒加载500次请求的情况下,您的线程池中需要2,500个线程,这意味着线程堆栈的内存为2.5 GB,无需任何操作。然后你就会遇到上下文切换的问题,这会严重影响你的机器的性能(影响机器上的所有服务,而不仅仅是你的Web应用程序)。尽管Windows在忽略等待线程方面做得相当不错,但它并不是用来处理如此大量的线程。请记住,当运行的线程数量等于机器上的逻辑CPU数量(通常不超过16)时,可以获得最高的效率。
因此增加线程池的大小是一个解决方案,而人们也一直在这样做了十年(甚至在微软自己的产品),它仅仅是少可扩展性和高效率,在内存和CPU方面使用情况,而且你总是处于可能导致饥饿的IO延迟突然增加的情况下。直到C#5.0之前,异步代码的复杂性对许多人来说都是不值得的。 async/await会像现在一样改变一切,您可以从异步IO的可伸缩性中受益,并且可以同时编写简单的代码。
更多细节:http://msdn.microsoft.com/en-us/library/ff647787.aspx“使用异步调用来调用Web服务或远程对象时,有执行额外的并行处理,而Web服务调用进行可能的话,应避免同步(阻塞)的机会调用Web服务,因为传出的Web服务调用是通过使用ASP.NET线程池中的线程来完成的。阻塞调用减少了处理其他传入请求的可用线程数。“
此回复不回答问题的第二部分。 – nunespascal 2012-06-12 04:23:36
为什么转向异步模式的理由很合理。 – eduncan911 2012-11-03 00:47:48
我不认为这解决了这样一个事实,即不管I/O是不可预测的,无论其他规定如何,用户在获得响应之前都必须等待一切完成。 http/web服务器本身可以处理更多负载的事实并不意味着它能够完全处理请求。我没有看到async如何解决这个问题,除了改变事物的分布方式以及可能引入更昂贵的上下文切换。使用asyc api的 – nilskp 2012-11-19 20:50:30