在高stream量情况下在ASP.NET中使用ThreadPool.QueueUserWorkItem
我一直认为使用ThreadPool(假设非关键)短命的后台任务被认为是最好的实践,即使在ASP.NET中,但是我接触到了这篇文章 ,似乎有其他的build议 -参数是你应该离开ThreadPool来处理ASP.NET相关的请求。
所以下面是我迄今为止一直在做的小型asynchronous任务:
ThreadPool.QueueUserWorkItem(s => PostLog(logEvent))
而文章build议,而不是明确创build一个线程,类似于:
new Thread(() => PostLog(logEvent)){ IsBackground = true }.Start()
第一种方法具有被pipe理和限制的优点,但是有潜力(如果文章是正确的话)后台任务正在争夺具有ASP.NET请求处理程序的线程。 第二种方法释放了ThreadPool,但代价是没有限制,因此可能会占用太多的资源。
所以我的问题是,文章中的build议是否正确?
如果您的网站stream量太大,以至于您的ThreadPool已经满了,那么最好还是带外,或者一个完整的ThreadPool意味着无论如何您都会受到资源的限制,在这种情况下,您不应该试图开始自己的线程?
澄清:我只是在小的非关键asynchronous任务(例如,远程日志logging)范围内请求,而不是昂贵的工作项目,这将需要一个单独的过程(在这种情况下,我同意你将需要一个更强大的解决scheme)。
其他答案似乎忽略了最重要的一点:
除非您正试图并行执行CPU密集型操作,以便在低负载的站点上更快地完成操作,否则根本没有必要使用工作线程。
这适用于由new Thread(...)
创build的空闲线程以及响应QueueUserWorkItem
请求的ThreadPool
中的工作线程。
是的,的确如此, 可以通过对太多的工作项进行排队来使ASP.NET进程中的ThreadPool
。 这将阻止ASP.NET处理进一步的请求。 文章中的信息在这方面是准确的; 用于QueueUserWorkItem
的同一线程池也用于服务请求。
但是,如果你实际上排队足够的工作项目,导致这个饥饿,那么你应该饿死线程池! 如果您正在同时运行数百个CPU密集型操作,那么在机器已经过载的情况下,另一个工作线程能够提供ASP.NET请求还有什么好处呢? 如果你遇到这种情况,你需要完全重新devise!
大多数情况下,我看到或听到multithreading代码在ASP.NET中被不恰当地使用,而不是排队等待CPU密集型工作。 这是排队I / O绑定的工作。 如果你想做I / O工作,那么你应该使用I / O线程(I / O完成端口)。
具体来说,您应该使用您使用的任何库类支持的asynchronouscallback。 这些方法总是非常清楚地标注; 他们从Begin
和End
Begin
。 如Stream.BeginRead
, Socket.BeginConnect
, WebRequest.BeginGetResponse
等。
这些方法确实使用ThreadPool
,但是它们使用IOCP,它们不会影响ASP.NET的请求。 它们是一种特殊的轻量级线程,可以通过来自I / O系统的中断信号“唤醒”。 而在ASP.NET应用程序中,每个工作线程通常都有一个I / O线程,因此每个请求可以有一个asynchronous操作排队。 这几乎是数百个asynchronous操作,没有任何显着的性能下降(假设I / O子系统可以跟上)。 这比你所需要的还要多。
请记住,asynchronous委托不会以这种方式工作 – 它们最终将使用工作线程,就像ThreadPool.QueueUserWorkItem
一样。 这只是.NET Framework库类的内置asynchronous方法,能够执行此操作。 你可以自己做,但是它很复杂,有点危险,可能超出了这个讨论的范围。
在我看来,这个问题的最佳答案是不要在ASP.NET中使用ThreadPool
或后台Thread
实例 。 这完全不像在Windows窗体应用程序中旋转一个线程,在这种情况下,您需要保持UI的响应,而不必关心它的效率。 在ASP.NET中,你关心的是吞吐量 ,所有这些工作线程的上下文切换无论是否使用ThreadPool
都会导致吞吐量的ThreadPool
。
如果您发现自己在ASP.NET中编写线程代码,请考虑是否可以重写使用预先存在的asynchronous方法,如果不能,请考虑您是否确实需要代码在后台线程中运行。 在大多数情况下,你可能会增加复杂性,没有净效益。
Microsoft的ASP.NET团队的Per Thomas Marquadt使用ASP.NET ThreadPool(QueueUserWorkItem)是安全的。
从文章 :
问)如果我的ASP.NET应用程序使用CLR ThreadPool线程,我会不会饿死ASP.NET,也使用CLR ThreadPool来执行请求? 大段引用
A)总结一下,不要担心让ASP.NET的线程挨饿,如果你觉得有问题,请告诉我,我们会照顾它的。
问)我应该创build自己的线程(新线程)? 这不会更好的ASP.NET,因为它使用CLR ThreadPool。
A)请不要。 或者换一种方式,不! 如果你真的很聪明 – 比我聪明 – 那么你可以创build自己的线程; 否则,别想它。 以下是您不应该经常创build新线程的一些原因:
1)与QueueUserWorkItem相比,这是非常昂贵的…顺便说一句,如果你能写一个比CLR更好的ThreadPool,我鼓励你去微软申请一份工作,因为我们一定在找像你这样的人! 。
网站不应该围绕产卵线程。
您通常将此function移出到Windows服务中,然后与之通信(我使用MSMQ与之通信)。
– 编辑
我在这里描述了一个实现: ASP.NET MVC Web应用程序中基于队列的后台处理
– 编辑
为了扩大为什么这比线程更好:
使用MSMQ,您可以与其他服务器通信。 你可以在机器上写一个队列,所以如果你确定出于某种原因,你的后台任务太多地占用了主服务器的资源,那么你可以把它转换得相当平凡。
它还允许您批量处理您正在尝试执行的任何任务(发送电子邮件/无论)。
我绝对认为在ASP.NET中快速,低优先级asynchronous工作的一般做法是使用.NET线程池,特别是对于高stream量的场景,因为您希望资源有界。
此外,线程的实现是隐藏的 – 如果你开始产生你自己的线程,你也必须正确地pipe理它们。 不是说你做不到,而是为什么要重塑那个轮子?
如果性能成为问题,并且可以确定线程池是限制因素(而不是数据库连接,传出networking连接,内存,页面超时等),那么可以调整线程池configuration以允许更多的工作线程,更高的排队请求等等
如果您没有性能问题,那么select产生新线程以减less与ASP.NET请求队列的争用是经典的过早优化。
理想情况下,您不需要使用单独的线程来执行日志logging操作,只需启用原始线程就可以尽快完成操作,这是MSMQ和单独的使用者线程/进程进入图片的地方。 我同意这是更重要的,更多的工作来实现,但你真的需要在这里的耐久性 – 一个共享的,内存中的队列的波动将迅速受到欢迎。
您应该使用QueueUserWorkItem,并避免创build像避免瘟疫一样的新线程。 为了解释为什么你不会饿死ASP.NET,因为它使用了相同的ThreadPool,想象一个非常熟练的玩杂耍的人,用两只手来保存六个保龄球钉,剑,或者任何在飞行中的东西。 想象一下为什么自己build立自己的线程是不好的,想象一下在高峰时段在西雅图发生的事情:大量使用的高速公路入口坡道使车辆能够立即进入车辆,而不是使用灯光,并将入口数量限制为每隔几秒。 最后,详细的解释请看这个链接:
谢谢,托马斯
那篇文章是不正确的。 ASP.NET拥有自己的线程池,pipe理工作线程,用于提供ASP.NET请求。 这个池通常是几百个线程,并且与ThreadPool池分开,这个池是处理器的一小部分。
在ASP.NET中使用ThreadPool不会干扰ASP.NET工作线程。 使用ThreadPool很好。
设置一个仅用于logging消息的单个线程,并使用生产者/消费者模式将日志消息传递到该线程也是可以接受的。 在这种情况下,由于线程是长期的,你应该创build一个新的线程来运行日志logging。
每条消息使用一个新的线程肯定是矫枉过正的。
另一种方法,如果你只是在谈论日志,就是使用像log4net这样的库。 它在单独的线程中处理日志logging,并负责处理该场景中可能出现的所有上下文问题。
我会说这篇文章是错误的。 如果你正在运行一个大的.NET商店,你可以在多个应用程序和多个网站(使用独立的应用程序池)中安全地使用这个池,只需要基于ThreadPool文档中的一个语句:
每个进程有一个线程池。 线程池的默认大小为每个可用处理器250个工作线程,以及1000个I / O完成线程。 线程池中的线程数可以使用SetMaxThreads方法进行更改。 每个线程使用默认堆栈大小,并以默认优先级运行。
上周我在工作中被问及类似的问题,我会给你同样的答案。 为什么你每个请求multithreadingWeb应用程序? Web服务器是一个非常棒的系统,可以及时提供很多请求(即multithreading)。 想想当你要求几乎任何网页的时候会发生什么事情。
- 请求某个页面
- Html送达
- Html告诉客户做进一步的请求(JS,CSS,图像等)。
- 进一步的信息被送回
你给出了远程日志logging的例子,但这应该是你的logging器的关注点。 应该有一个asynchronous过程来及时接收消息。 山姆甚至指出,你的logging器(log4net)应该已经支持这个了。
山姆也是正确的,因为在CLR上使用线程池不会导致IIS中线程池的问题。 这里要关心的是,你不是从一个进程产生线程,而是从IIS线程池线程产生新的线程。 有一个区别,区分是重要的。
线程与进程
线程和进程都是并行化应用程序的方法。 但是,进程是独立的执行单元,它们包含自己的状态信息,使用自己的地址空间,并且只能通过进程间通信机制(通常由操作系统pipe理)相互作用。 在devise阶段,应用程序通常分为多个进程,而主进程明确地产生subprocess,因为在逻辑上区分重要的应用程序function是有意义的。 换句话说,过程是一个build筑结构。
相比之下,线程是一种编码结构,不会影响应用程序的体系结构。 一个进程可能包含多个线程; 进程内的所有线程共享相同的状态和相同的内存空间,并且可以直接相互通信,因为它们共享相同的variables。
资源
我不同意引用的文章(C#feeds.com)。 创build一个新的线程很容易,但很危险。 在单个内核上运行的活动线程的最佳数量实际上是惊人的低 – 小于10.如果创build线程用于次要任务,那么导致机器浪费时间切换线程太容易了。 线程是需要pipe理的资源。 WorkItem抽象是在那里来处理这个。
在减less可用于请求的线程数量和创build太multithreading以允许其中任何一个线程有效地处理之间存在折衷。 这是一个非常dynamic的情况,但我认为应该主动pipe理一个(在这种情况下由线程池),而不是把它留给处理器,以保持在线程创build之前。
最后,本文对使用ThreadPool的危险性做了一些相当广泛的陈述,但它确实需要一些具体的备份。
IIS是否使用相同的ThreadPool来处理传入的请求似乎很难得到一个明确的答案,似乎也已经改变了版本。 因此,不要过多地使用ThreadPool线程,这样IIS有很多可用。 另一方面,为每一个小任务产生自己的线程似乎是一个坏主意。 据推测,你在日志logging中有某种locking,所以一次只能有一个线程进行,其余的只是轮stream进行计划和计划(更不用说产生新线程的开销)。 本质上,你遇到了ThreadPooldevise要避免的确切问题。
看来,合理的妥协将是您的应用程序分配一个可以传递消息的单一日志logging线程。 你会小心发送消息尽可能快,这样你就不会减慢你的应用程序。