以下
之间有什么区别ThreadPool.QueueUserWorkItem
VS
Task.Factory.StartNew
如果对于某些长时间运行的任务调用上述代码500次,是否意味着将占用所有线程池线程?
或者TPL(第二选项)是否足够聪明,只能占用少于或等于处理器数量的线程?
答案 0 :(得分:89)
如果您要使用TPL启动长时间运行的任务,则应指定TaskCreationOptions.LongRunning
,这意味着不在线程池上安排它。 (编辑:正如评论中所指出的,这个是特定于调度程序的决定,并不是一个硬性和快速的保证,但我希望任何合理的生产调度程序都能避免安排长时间运行的任务在线程池上。)
您绝对不应该自己在线程池上安排大量长时间运行的任务。我相信这些天线程池的默认大小非常大(因为它经常以这种方式滥用)但从根本上说它不应该像这样使用。
线程池的目的是避免短任务在创建新线程时受到重大影响,与实际运行时相比。如果任务将运行很长时间,那么创建新线程的影响无论如何都会相对较小 - 并且您不希望最终可能耗尽线程池线程。 (现在不太可能,但我在早期版本的.NET上体验过它。)
就个人而言,如果我有选项,我肯定会使用TPL,因为Task
API非常好 - 但做记得告诉TPL你希望任务能够跑了很长时间。
编辑:如评论中所述,另请参阅PFX团队关于choosing between the TPL and the thread pool的博文:
总之,我将重申CLR团队的ThreadPool开发人员已经说过的内容:
Task is now the preferred way to queue work to the thread pool.
编辑:同样来自评论,不要忘记TPL允许您使用custom schedulers,如果您真的想...
答案 1 :(得分:0)
不,通过使用 ThreadPool
方法(或更现代的 Task.Factory.StartNew
方法),在利用 Task.Run
线程的方式中没有增加额外的聪明。调用 Task.Factory.StartNew
500 次(对于长时间运行的任务)肯定会使 ThreadPool
饱和,并且会使其长时间保持饱和。这不是一个好情况,因为饱和的 ThreadPool
会对在这 500 个启动任务期间也可能处于活动状态的任何其他独立回调、计时器事件、异步延续等产生负面影响。
Task.Factory.StartNew
方法在 TaskScheduler.Current
上调度所提供的 Action
的执行,默认情况下为 TaskScheduler.Default
,即 internal
{{ 3}} 类。这是 ThreadPoolTaskScheduler.QueueTask
方法的 ThreadPoolTaskScheduler
:
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
上。 the implementation 是 internal
类的 ThreadPool
方法,具有一些未公开的细微差别 (bool forceGlobal
)。但是当 ThreadPool
变得饱和时,它并没有改变它的行为¹。目前,这种行为也不是特别复杂。线程注入算法只是每 500 毫秒向池中注入一个新线程,直到饱和事件结束。
¹ 当工作需求超过当前线程的可用性时,就说ThreadPool
饱和,超过阈值ThreadPool.UnsafeQueueCustomWorkItem
不再按需创建新线程到达。