我一直认为,即使在 ASP.NET 中,使用 ThreadPool 执行(比方说非关键的)短期后台任务也被认为是最佳实践,但后来我遇到了 这篇文章似乎另有暗示 - 论点是您应该让 ThreadPool 来处理 ASP.NET 相关请求。
因此,到目前为止,我一直在执行小型异步任务:
ThreadPool.QueueUserWorkItem(s => PostLog(logEvent))
文章建议改为显式创建线程,类似于:
new Thread(() => PostLog(logEvent)){ IsBackground = true }.Start()
第一种方法具有受管理和限制的优点,但有潜力(如果文章正确)然后后台任务会与 ASP.NET 请求处理程序争夺线程。第二种方法释放线程池,但代价是不受限制,因此可能会占用太多资源。
所以我的问题是,文章中的建议正确吗?
如果您的网站流量如此之大,以至于您的线程池已满,那么最好采用带外,还是满的线程池意味着您已经达到了资源的限制,在这种情况下,您不应该尝试启动自己的线程吗?
澄清:我只是在小型非关键异步任务(例如远程日志记录)的范围内询问,而不是需要单独流程的昂贵工作项目(在这些情况下,我同意您需要更强大的解决方案)。
I've always been under the impression that using the ThreadPool for (let's say non-critical) short-lived background tasks was considered best practice, even in ASP.NET, but then I came across this article that seems to suggest otherwise - the argument being that you should leave the ThreadPool to deal with ASP.NET related requests.
So here's how I've been doing small asynchronous tasks so far:
ThreadPool.QueueUserWorkItem(s => PostLog(logEvent))
And the article is suggesting instead to create a thread explicitly, similar to:
new Thread(() => PostLog(logEvent)){ IsBackground = true }.Start()
The first method has the advantage of being managed and bounded, but there's the potential (if the article is correct) that the background tasks are then vying for threads with ASP.NET request-handlers. The second method frees up the ThreadPool, but at the cost of being unbounded and thus potentially using up too many resources.
So my question is, is the advice in the article correct?
If your site was getting so much traffic that your ThreadPool was getting full, then is it better to go out-of-band, or would a full ThreadPool imply that you're getting to the limit of your resources anyway, in which case you shouldn't be trying to start your own threads?
Clarification: I'm just asking in the scope of small non-critical asynchronous tasks (eg, remote logging), not expensive work items that would require a separate process (in these cases I agree you'll need a more robust solution).
发布评论
评论(11)
这里的其他答案似乎遗漏了最重要的一点:
除非您尝试并行化 CPU 密集型操作以便在低负载站点上更快地完成它,否则使用工作线程是没有意义的
这适用于由
new Thread(...)
创建的自由线程,以及ThreadPool
中响应的工作线程QueueUserWorkItem 请求。
是的,确实如此,您可以通过对太多工作项进行排队来使 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
,但它们使用IOCP,而IOCP不会干扰 ASP.NET 请求。它们是一种特殊的轻量级线程,可以被来自 I/O 系统的中断信号“唤醒”。在 ASP.NET 应用程序中,通常每个工作线程都有一个 I/O 线程,因此每个请求都可以有一个排队的异步操作。这实际上是数百个异步操作,而没有任何显着的性能下降(假设 I/O 子系统可以跟上)。它比您所需要的要多得多。请记住,异步委托不会以这种方式工作 - 它们最终会使用工作线程,就像
ThreadPool.QueueUserWorkItem
一样。只有 .NET Framework 库类的内置异步方法才能够执行此操作。您可以自己做,但它很复杂,而且有点危险,可能超出了本讨论的范围。在我看来,这个问题的最佳答案是不要在 ASP 中使用
ThreadPool
或后台Thread
实例。 NET。它与在 Windows 窗体应用程序中旋转线程完全不同,在 Windows 窗体应用程序中,您这样做是为了保持 UI 响应,而不关心它的效率如何。在 ASP.NET 中,您关心的是吞吐量,无论您是否使用ThreadPool<,所有这些工作线程上的所有上下文切换都绝对会杀死您的吞吐量/code> 或不。
如果您发现自己在 ASP.NET 中编写线程代码,请考虑是否可以重写它以使用预先存在的异步方法,如果不能,请考虑您是否真的需要该代码完全在后台线程中运行。在大多数情况下,您可能会增加复杂性而没有任何净收益。
Other answers here seem to be leaving out the most important point:
Unless you are trying to parallelize a CPU-intensive operation in order to get it done faster on a low-load site, there is no point in using a worker thread at all.
That goes for both free threads, created by
new Thread(...)
, and worker threads in theThreadPool
that respond toQueueUserWorkItem
requests.Yes, it's true, you can starve the
ThreadPool
in an ASP.NET process by queuing too many work items. It will prevent ASP.NET from processing further requests. The information in the article is accurate in that respect; the same thread pool used forQueueUserWorkItem
is also used to serve requests.But if you are actually queuing enough work items to cause this starvation, then you should be starving the thread pool! If you are running literally hundreds of CPU-intensive operations at the same time, what good would it do to have another worker thread to serve an ASP.NET request, when the machine is already overloaded? If you're running into this situation, you need to redesign completely!
Most of the time I see or hear about multi-threaded code being inappropriately used in ASP.NET, it's not for queuing CPU-intensive work. It's for queuing I/O-bound work. And if you want to do I/O work, then you should be using an I/O thread (I/O Completion Port).
Specifically, you should be using the async callbacks supported by whatever library class you're using. These methods are always very clearly labeled; they start with the words
Begin
andEnd
. As inStream.BeginRead
,Socket.BeginConnect
,WebRequest.BeginGetResponse
, and so on.These methods do use the
ThreadPool
, but they use IOCPs, which do not interfere with ASP.NET requests. They are a special kind of lightweight thread that can be "woken up" by an interrupt signal from the I/O system. And in an ASP.NET application, you normally have one I/O thread for each worker thread, so every single request can have one async operation queued up. That's literally hundreds of async operations without any significant performance degradation (assuming the I/O subsystem can keep up). It's way more than you'll ever need.Just keep in mind that async delegates do not work this way - they'll end up using a worker thread, just like
ThreadPool.QueueUserWorkItem
. It's only the built-in async methods of the .NET Framework library classes that are capable of doing this. You can do it yourself, but it's complicated and a little bit dangerous and probably beyond the scope of this discussion.The best answer to this question, in my opinion, is don't use the
ThreadPool
or a backgroundThread
instance in ASP.NET. It's not at all like spinning up a thread in a Windows Forms application, where you do it to keep the UI responsive and don't care about how efficient it is. In ASP.NET, your concern is throughput, and all that context switching on all those worker threads is absolutely going to kill your throughput whether you use theThreadPool
or not.Please, if you find yourself writing threading code in ASP.NET - consider whether or not it could be rewritten to use pre-existing asynchronous methods, and if it can't, then please consider whether or not you really, truly need the code to run in a background thread at all. In the majority of cases, you will probably be adding complexity for no net benefit.
根据 Microsoft ASP.NET 团队的 Thomas Marquadt 的说法,使用 ASP.NET ThreadPool (QueueUserWorkItem) 是安全的。
摘自文章:
Per Thomas Marquadt of the ASP.NET team at Microsoft, it is safe to use the ASP.NET ThreadPool (QueueUserWorkItem).
From the article:
网站不应该绕过生成线程。
您通常会将此功能移至 Windows 服务中,然后与之通信(我使用 MSMQ 与它们通信)。
-- 编辑
我在这里描述了一个实现: 基于队列ASP.NET MVC Web 应用程序中的后台处理
- 编辑
要详细说明为什么这比线程更好:
使用 MSMQ,您可以与另一台服务器进行通信。您可以跨机器写入队列,因此,如果您出于某种原因确定后台任务过多地消耗了主服务器的资源,您只需简单地转移它即可。
它还允许您批量处理您尝试执行的任何任务(发送电子邮件/其他内容)。
Websites shouldn't go around spawning threads.
You typically move this functionality out into a Windows Service that you then communicate with (I use MSMQ to talk to them).
-- Edit
I described an implementation here: Queue-Based Background Processing in ASP.NET MVC Web Application
-- Edit
To expand why this is even better than just threads:
Using MSMQ, you can communicate to another server. You can write to a queue across machines, so if you determine, for some reason, that your background task is using up the resources of the main server too much, you can just shift it quite trivially.
It also allows you to batch-process whatever task you were trying to do (send emails/whatever).
我绝对认为 ASP.NET 中快速、低优先级异步工作的一般做法是使用 .NET 线程池,特别是对于高流量场景,因为您希望资源受到限制。
此外,线程的实现是隐藏的 - 如果您开始生成自己的线程,则还必须正确管理它们。并不是说你做不到,但为什么要重新发明那个轮子呢?
如果性能成为问题,并且您可以确定线程池是限制因素(而不是数据库连接、传出网络连接、内存、页面超时等),那么您可以调整线程池配置以允许更多工作线程、更高的排队请求等等。
如果您没有性能问题,那么选择生成新线程以减少与 ASP.NET 请求队列的争用是典型的过早优化。
理想情况下,您不需要使用单独的线程来执行日志记录操作 - 只需使原始线程尽快完成操作,这就是 MSMQ 和单独的消费者线程/进程发挥作用的地方。我同意,这需要实现的工作量更大、工作量更大,但这里确实需要持久性 - 共享内存队列的波动性很快就会失去其受欢迎程度。
I definitely think that general practice for quick, low-priority asynchronous work in ASP.NET would be to use the .NET thread pool, particularly for high-traffic scenarios as you want your resources to be bounded.
Also, the implementation of threading is hidden - if you start spawning your own threads, you have to manage them properly as well. Not saying you couldn't do it, but why reinvent that wheel?
If performance becomes an issue, and you can establish that the thread pool is the limiting factor (and not database connections, outgoing network connections, memory, page timeouts etc) then you tweak the thread pool configuration to allow more worker threads, higher queued requests, etc.
If you don't have a performance problem then choosing to spawn new threads to reduce contention with the ASP.NET request queue is classic premature optimization.
Ideally you wouldn't need to use a separate thread to do a logging operation though - just enable the original thread to complete the operation as quickly as possible, which is where MSMQ and a separate consumer thread / process come in to the picture. I agree that this is heavier and more work to implement, but you really need the durability here - the volatility of a shared, in-memory queue will quickly wear out its welcome.
您应该使用 QueueUserWorkItem,并避免创建新线程,就像避免瘟疫一样。为了解释为什么您不会挨饿 ASP.NET,因为它使用相同的线程池,请想象一个非常熟练的杂耍演员用两只手保持六个保龄球瓶、剑或其他东西在飞行。为了直观地了解为什么创建自己的线程是不好的,想象一下在西雅图的高峰时段会发生什么,当时大量使用的高速公路入口坡道允许车辆立即进入交通,而不是使用交通灯并将入口数量限制为每隔几秒一个。最后,详细解释请参见此链接:
http://blogs.msdn.com/tmarq/archive/2010/04/14/performing-asynchronous-work-or-tasks-in-asp-net- applications.aspx
谢谢,
托马斯
You should use QueueUserWorkItem, and avoid creating new threads like you would avoid the plague. For a visual that explains why you won't starve ASP.NET, since it uses the same ThreadPool, imagine a very skilled juggler using two hands to keep a half dozen bowling pins, swords, or whatever in flight. For a visual of why creating your own threads is bad, imagine what happens in Seattle at rush hour when heavily used entrance ramps to the highway allow vehicles to enter traffic immediately instead of using a light and limiting the number of entrances to one every few seconds. Finally, for a detailed explanation, please see this link:
http://blogs.msdn.com/tmarq/archive/2010/04/14/performing-asynchronous-work-or-tasks-in-asp-net-applications.aspx
Thanks,
Thomas
那篇文章不正确。 ASP.NET 有自己的线程池(托管工作线程),用于服务 ASP.NET 请求。该池通常有几百个线程,并且与 ThreadPool 池分开,后者是处理器的较小倍数。
在 ASP.NET 中使用 ThreadPool 不会干扰 ASP.NET 工作线程。使用ThreadPool就可以了。
设置一个仅用于记录消息并使用生产者/消费者模式将日志消息传递给该线程的线程也是可以接受的。在这种情况下,由于线程是长期存在的,因此您应该创建一个新线程来运行日志记录。
对每条消息使用一个新线程绝对是矫枉过正。
如果您只谈论日志记录,另一种选择是使用像 log4net 这样的库。它在单独的线程中处理日志记录,并处理该场景中可能出现的所有上下文问题。
That article is not correct. ASP.NET has it's own pool of threads, managed worker threads, for serving ASP.NET requests. This pool is usually a few hundred threads and is separate from the ThreadPool pool, which is some smaller multiple of processors.
Using ThreadPool in ASP.NET will not interfere with ASP.NET worker threads. Using ThreadPool is fine.
It would also be acceptable to setup a single thread which is just for logging messages and using producer/consumer pattern to pass logs messages to that thread. In that case, since the thread is long-lived, you should create a single new thread to run the logging.
Using a new thread for every message is definitely overkill.
Another alternative, if you're only talking about logging, is to use a library like log4net. It handles logging in a separate thread and takes care of all the context issues that could come up in that scenario.
我想说这篇文章是错误的。如果您正在运营一家大型 .NET 商店,您可以安全地跨多个应用程序和多个网站使用该池(使用单独的应用程序池),只需根据 ThreadPool 文档:
I'd say the article is wrong. If you're running a large .NET shop you can safely use the pool across multiple apps and multiple websites (using seperate app pools), simply based on one statement in the ThreadPool documentation:
上周我在工作中被问到了类似的问题,我会给你同样的答案。为什么每个请求都使用多线程 Web 应用程序? Web 服务器是一个经过大量优化的出色系统,可以及时提供许多请求(即多线程)。想一想当您请求网络上的几乎任何页面时会发生什么。
您给出了远程日志记录的示例,但这应该是一个问题你的记录器。应建立异步流程来及时接收消息。 Sam 甚至指出您的记录器 (log4net) 应该已经支持这一点。
Sam 的观点也是正确的,即在 CLR 上使用线程池不会导致 IIS 中的线程池出现问题。不过,这里要关心的是,您不是从进程中生成线程,而是从 IIS 线程池线程中生成新线程。这是有区别的,而且区别很重要。
来源
I was asked a similar question at work last week and I'll give you the same answer. Why are you multi threading web applications per request? A web server is a fantastic system optimized heavily to provide many requests in a timely fashion (i.e. multi threading). Think of what happens when you request almost any page on the web.
You give the example of remote logging, but that should be a concern of your logger. An asynchronous process should be in place to receive messages in a timely fashion. Sam even points out that your logger (log4net) should already support this.
Sam is also correct in that using the Thread Pool on the CLR will not cause issues with the thread pool in IIS. The thing to be concerned with here though, is that you are not spawning threads from a process, you are spawning new threads off of IIS threadpool threads. There is a difference and the distinction is important.
Source
您可以使用 Parallel.For 或 Parallel.ForEach 并定义要分配的可能线程的限制,以平稳运行并防止池不足。
但是,要在后台运行,您将需要在 ASP.Net Web 应用程序中使用下面的纯 TPL 样式。
You can use Parallel.For or Parallel.ForEach and define the limit of possible threads you want to allocate to run smoothly and prevent pool starvation.
However, being run in background you will need to use pure TPL style below in ASP.Net web application.
我不同意引用的文章(C#feeds.com)。创建新线程很容易,但很危险。在单个核心上运行的最佳活动线程数实际上低得惊人 - 不到 10 个。如果为次要任务创建线程,则很容易导致机器浪费时间切换线程。线程是一种需要管理的资源。 WorkItem 抽象就是用来处理这个问题的。
这里需要权衡减少可用于请求的线程数量和创建太多线程以允许其中任何线程有效处理。这是一种非常动态的情况,但我认为应该主动管理(在本例中由线程池),而不是将其留给处理器以保持在线程创建之前。
最后,本文对使用 ThreadPool 的危险做了一些非常全面的陈述,但它确实需要一些具体的东西来支持它们。
I do not agree with the referenced article(C#feeds.com). It is easy to create a new thread but dangerous. The optimal number of active threads to run on a single core is actually surprisingly low - less than 10. It is way too easy to cause the machine to waste time switching threads if threads are created for minor tasks. Threads are a resource that REQUIRE management. The WorkItem abstraction is there to handle this.
There is a trade off here between reducing the number of threads available for requests and creating too many threads to allow any of them to process efficiently. This is a very dynamic situation but I think one that should be actively managed (in this case by the thread pool) rather than leaving it to the processer to stay ahead of the creation of threads.
Finally the article makes some pretty sweeping statements about the dangers of using the ThreadPool but it really needs something concrete to back them up.
IIS 是否使用相同的 ThreadPool 来处理传入请求似乎很难得到明确的答案,而且似乎也随着版本的不同而发生了变化。因此,不要过度使用 ThreadPool 线程似乎是个好主意,这样 IIS 就有很多可用的线程。另一方面,为每个小任务生成自己的线程似乎是一个坏主意。据推测,您在日志记录中进行了某种锁定,因此一次只能有一个线程进行,其余线程将轮流进行计划和未计划(更不用说生成新线程的开销)。本质上,您遇到的正是 ThreadPool 旨在避免的问题。
似乎合理的妥协是让您的应用程序分配一个可以向其传递消息的日志记录线程。您需要注意发送消息的速度尽可能快,以免减慢应用程序的速度。
Whether or not IIS uses the same ThreadPool to handle incoming requests seems hard to get a definitive answer to, and also seems to have changed over versions. So it would seem like a good idea not to use ThreadPool threads excessively, so that IIS has a lot of them available. On the other hand, spawning your own thread for every little task seems like a bad idea. Presumably, you have some sort of locking in your logging, so only one thread could progress at a time, and the rest would just take turns getting scheduled and unscheduled (not to mention the overhead of spawning a new thread). Essentially, you run into the exact problems the ThreadPool was designed to avoid.
It seems that a reasonable compromise would be for your app to allocate a single logging thread that you could pass messages to. You would want to be careful that sending messages is as fast as possible so that you don't slow down your app.