ThreadPool.QueueUserWorkItem 与 Task.Factory.StartNew

发布于 2025-01-03 13:57:58 字数 219 浏览 4 评论 0原文

下面

ThreadPool.QueueUserWorkItem

Task.Factory.StartNew

如果上面的代码对于某个长时间运行的任务被调用 500 次,是否意味着所有线程池线程都将被占用?

或者 TPL(第二个选项)是否足够聪明,只占用少于或等于处理器数量的线程?

What is difference between the below

ThreadPool.QueueUserWorkItem

vs

Task.Factory.StartNew

If the above code is called 500 times for some long running task, does it mean all the thread pool threads will be taken up?

Or will TPL (2nd option) be smart enough to just take up threads less or equal to number of processors?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

旧夏天 2025-01-10 13:57:58

如果要使用 TPL 启动长时间运行的任务,则应指定 TaskCreationOptions.LongRunning,这意味着它不会将其调度到线程池上。 (编辑:正如评论中所指出的,这是一个特定于调度程序的决定,并不是一个硬性和快速的保证,但我希望任何明智的生产调度程序都会避免调度长时间运行的任务在线程池上。)

您绝对不应该自己在线程池上安排大量长时间运行的任务。我相信现在线程池的默认大小相当大(因为它经常以这种方式被滥用),但从根本上讲,它不应该这样使用。

线程池的目的是避免任务因创建新线程而受到与实际运行时间相比的较大影响。如果任务将运行很长时间,那么创建新线程的影响无论如何都会相对较小 - 并且您不希望最终可能耗尽线程池线程。 (现在不太可能,但我确实在早期版本的.NET 上经历过这种情况。)

就我个人而言,如果可以选择,我肯定会使用 TPL,因为 Task API 非常好 - 但一定记得告诉 TPL 您希望任务运行很长时间。

编辑:如评论中所述,另请参阅 PFX 团队的博客文章 在 TPL 和线程池之间进行选择

最后,我将重申 CLR 团队的 ThreadPool 开发人员已经说过的话:

任务现在是将工作排队到线程池的首选方式。

编辑:同样从评论中,不要忘记 TPL 允许您使用 自定义调度程序,如果你真的想...

If you're going to start a long-running task with TPL, you should specify TaskCreationOptions.LongRunning, which will mean it doesn't schedule it on the thread-pool. (EDIT: As noted in comments, this is a scheduler-specific decision, and isn't a hard and fast guarantee, but I'd hope that any sensible production scheduler would avoid scheduling long-running tasks on a thread pool.)

You definitely shouldn't schedule a large number of long-running tasks on the thread pool yourself. I believe that these days the default size of the thread pool is pretty large (because it's often abused in this way) but fundamentally it shouldn't be used like this.

The point of the thread pool is to avoid short tasks taking a large hit from creating a new thread, compared with the time they're actually running. If the task will be running for a long time, the impact of creating a new thread will be relatively small anyway - and you don't want to end up potentially running out of thread pool threads. (It's less likely now, but I did experience it on earlier versions of .NET.)

Personally if I had the option, I'd definitely use TPL on the grounds that the Task API is pretty nice - but do remember to tell TPL that you expect the task to run for a long time.

EDIT: As noted in comments, see also the PFX team's blog post on choosing between the TPL and the thread pool:

In conclusion, I’ll reiterate what the CLR team’s ThreadPool developer has already stated:

Task is now the preferred way to queue work to the thread pool.

EDIT: Also from comments, don't forget that TPL allows you to use custom schedulers, if you really want to...

肥爪爪 2025-01-10 13:57:58

不,ThreadPool 线程通过使用 Task.Factory.StartNew 方法(或更现代的 Task.Run 方法)。调用 Task.Factory.StartNew 500 次(对于长时间运行的任务)肯定会使 ThreadPool 饱和,并且会使其在很长一段时间内保持饱和。这不是一个好的情况,因为饱和的线程池会对在此 500 个启动任务期间也可能处于活动状态的任何其他独立回调、计时器事件、异步延续等产生负面影响。

Task.Factory.StartNew 方法安排在 TaskScheduler.Current,默认为 TaskScheduler.Default,这是内部 ThreadPoolTask​​Scheduler 类。这是 的实现>ThreadPoolTask​​Scheduler.QueueTask方法:

protected internal override void QueueTask(Task task)
{
    if ((task.Options & TaskCreationOptions.LongRunning) != 0)
    {
        // Run LongRunning tasks on their own dedicated thread.
        Thread thread = new Thread(s_longRunningThreadWork);
        thread.IsBackground = true; // Keep this thread from blocking process shutdown
        thread.Start(task);
    }
    else
    {
        // Normal handling for non-LongRunning tasks.
        bool forceToGlobalQueue = ((task.Options & TaskCreationOptions.PreferFairness) != 0);
        ThreadPool.UnsafeQueueCustomWorkItem(task, forceToGlobalQueue);
    }
}

可以看到任务的执行被调度在无论如何,线程池。 ThreadPool.UnsafeQueueCustomWorkItemThreadPool 类的一个内部方法,并且有一些细微差别(bool forceGlobal))未公开暴露。但是,当线程池饱和时,它不会改变它的行为。目前,这种行为也不是特别复杂。线程注入算法只是每 1000 毫秒向池中注入一个新线程,直到饱和事件结束。

1 当工作需求超过线程的当前可用性时,ThreadPool 被称为饱和,并且阈值SetMinThreads 已达到,超过该值不再按需创建新线程。

No, there is no extra cleverness added in the way the ThreadPool threads are utilized, by using the Task.Factory.StartNew method (or the more modern Task.Run method). Calling Task.Factory.StartNew 500 times (with long running tasks) is certainly going to saturate the ThreadPool, and will keep it saturated for a long time. Which is not a good situation to have, because a saturated ThreadPool affects negatively any other independent callbacks, timer events, async continuations etc that may also be active during this 500-launched-tasks period.

The Task.Factory.StartNew method schedules the execution of the supplied Action on the TaskScheduler.Current, which by default is the TaskScheduler.Default, which is the internal ThreadPoolTaskScheduler class. Here is the implementation of the ThreadPoolTaskScheduler.QueueTask method:

protected internal override void QueueTask(Task task)
{
    if ((task.Options & TaskCreationOptions.LongRunning) != 0)
    {
        // Run LongRunning tasks on their own dedicated thread.
        Thread thread = new Thread(s_longRunningThreadWork);
        thread.IsBackground = true; // Keep this thread from blocking process shutdown
        thread.Start(task);
    }
    else
    {
        // Normal handling for non-LongRunning tasks.
        bool forceToGlobalQueue = ((task.Options & TaskCreationOptions.PreferFairness) != 0);
        ThreadPool.UnsafeQueueCustomWorkItem(task, forceToGlobalQueue);
    }
}

As you can see the execution of the task is scheduled on the ThreadPool anyway. The ThreadPool.UnsafeQueueCustomWorkItem is an internal method of the ThreadPool class, and has some nuances (bool forceGlobal) that are not publicly exposed. But there is nothing in it that changes the behavior of the ThreadPool when it becomes saturated¹. This behavior, currently, is not particularly sophisticated either. The thread-injection algorithm just injects one new thread in the pool every 1000 msec, until the saturation incident ends.

¹ The ThreadPool is said to be saturated when the demand for work surpasses the current availability of threads, and the threshold SetMinThreads above which new threads are no longer created on demand has been reached.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文