这里的其他答案似乎遗漏了最重要的一点:
除非你试图在低负载网站上并行执行CPU密集型操作以加快完成速度,否则根本没有必要使用工作线程。
这适用于通过 new Thread(...)
创建的空闲线程以及响应 QueueUserWorkItem
请求的 ThreadPool
中的工作线程。
是的,没错,如果排队太多的工作项,你可以使ASP.NET进程中的ThreadPool
饿死。 这将防止ASP.NET处理更进一步的请求。 这篇文章中提供的信息在这方面是准确的;使用QueueUserWorkItem
的同一个线程池也用于为请求提供服务。
但是,如果你实际上正在排队足够多的工作项以导致这种饥饿,那么你就应该让线程池饿死! 当同时运行数百个CPU密集型操作时,当机器已经超载时,再增加另一个工作线程来服务ASP.NET请求有什么好处呢? 如果你遇到这种情况,你需要完全重新设计!
大多数时候我看到或听到关于在ASP.NET中不适当使用多线程代码的情况,都不是用于排队CPU密集型工作。 它们用于排队I/O绑定工作。 如果你想执行I/O工作,则应该使用I/O线程(I/O完成端口)。
具体而言,你应该使用所使用的库类支持的异步回调。 这些方法始终非常清楚地标记出来; 它们以单词 Begin
和 End
开头,例如: Stream.BeginRead
,Socket.BeginConnect
,WebRequest.BeginGetResponse
等等。
这些方法确实使用了
ThreadPool
,但它们使用的是IOCPs,这些不会干扰ASP.NET请求的特殊轻量级线程。它们可以被I/O系统的中断信号“唤醒”。在ASP.NET应用程序中,通常每个工作线程都有一个I/O线程,因此每个请求都可以有一个异步操作排队。这意味着成百上千个异步操作而没有任何显著的性能下降(假设I/O子系统跟得上)。这比你需要的还要多得多。
请记住,异步委托不是这样工作的——它们最终会像
ThreadPool.QueueUserWorkItem
一样使用工作线程。只有.NET Framework库类的内置异步方法才能做到这点。您可以自己做,但这很复杂、有点危险,可能超出了本讨论的范围。
在我看来,对于这个问题,最好的答案是
不要在ASP.NET中使用ThreadPool
或后台Thread
实例。这与在Windows窗体应用程序中启动线程以保持UI响应并不相同,你并不关心它的有效性。在ASP.NET中,你关心的是吞吐量,所有那些工作线程上的上下文切换绝对会使你的吞吐量下降,无论你是否使用
ThreadPool
。
如果您发现自己在ASP.NET中编写线程代码,请考虑它是否可以重写为使用现有的异步方法,如果不能,请再三考虑您是否真正需要在后台线程中运行该代码。在大多数情况下,您可能只是增加了复杂性,而没有任何净收益。