ThreadPool.QueueUserWorkItem vs Task.Factory.StartNew

本文关键字:Factory StartNew Task vs QueueUserWorkItem ThreadPool | 更新日期: 2023-09-27 18:23:44

以下有什么区别

ThreadPool.QueueUserWorkItem

Task.Factory.StartNew

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

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

ThreadPool.QueueUserWorkItem vs Task.Factory.StartNew

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

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

线程

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

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

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

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

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

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

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

Task.Factory.StartNew方法调度在TaskScheduler.Current上执行提供的Action,默认情况下是TaskScheduler.Default,这是internal ThreadPoolTaskScheduler类。以下是ThreadPoolTaskScheduler.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上安排的。ThreadPool.UnsafeQueueCustomWorkItemThreadPool类的internal方法,并且有一些细微差别(bool forceGlobal(没有公开。但是,当ThreadPool饱和¹时,它没有任何内容可以改变它的行为。目前,这种行为也不是特别复杂。线程注入算法每 1000 毫秒在池中注入一个新线程,直到饱和事件结束。

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