Task.Factory.StartNew或Parallel.ForEach用于许多长时间运行的任务?

时间:2012-05-21 15:08:38

标签: c# .net c#-4.0 task-parallel-library parallel-for

  

可能重复:
  Parallel.ForEach vs Task.Factory.StartNew

我需要每晚在ThreadPool内运行大约1,000个任务(这个数字可能会在未来增长)。每项任务都执行长时间运行(从Web服务读取数据),并且不是CPU密集型Async I/O不适用于此特定用例。

给定IList<string>个参数,我需要DoSomething(string x)。我试图在以下两个选项之间进行选择:

IList<Task> tasks = new List<Task>();
foreach (var p in parameters)
{
    tasks.Add(Task.Factory.StartNew(() => DoSomething(p), TaskCreationOptions.LongRunning));
}
Task.WaitAll(tasks.ToArray());

OR

Parallel.ForEach(parameters, new ParallelOptions {MaxDegreeOfParallelism = Environment.ProcessorCount*32}, DoSomething);

哪个选项更好,为什么?

注意:

答案应包括TaskCreationOptions.LongRunningMaxDegreeOfParallelism = Environment.ProcessorCount * SomeConstant的使用情况之间的比较。

3 个答案:

答案 0 :(得分:35)

也许你没有意识到这一点,但Parallel类中的成员只是Task对象的简单(复杂)包装。如果您想知道,Parallel类会使用Task创建TaskCreationOptions.None个对象。但是,无论将任何创建选项传递给任务对象的构造函数,MaxDegreeOfParallelism都会影响这些任务对象。

TaskCreationOptions.LongRunning向底层TaskScheduler提供了一个“提示”,表明它可能会因超额订阅线程而表现更好。超额订阅适用于具有高延迟的线程,例如I / O,因为它会将多个线程(是线程,而不是任务)分配给单个内核,以便它总是有事情要做,而不是等待线程处于等待状态时完成的操作。在使用TaskScheduler的{​​{1}}上,在他们自己的专用线程上运行LongRunning任务(每个任务都有一个线程的唯一情况),否则它将运行通常,有安排和工作偷窃(真的,无论如何你想要的)

ThreadPool控制运行的并发操作数。它类似于指定数据将被拆分和处理的最大分区数。如果能够指定MaxDegreeOfParallelism,那么所有这一切都将限制一次运行的任务数量,类似于其最大并发级别设置为该值的TaskCreationOptions.LongRunning {{ 3}}

您可能需要TaskScheduler。但是,添加Parallel.ForEach等于这么高的数字实际上并不能保证会有多个线程同时运行,因为任务仍将由MaxDegreeOfParallelism控制。该调度程序将一次运行的线程数量尽可能地减少,我认为这是两种方法之间的最大差异。你可以编写(并指定)你自己的ThreadPoolTaskScheduler,它可以模仿最大程度的并行行为,并且拥有两全其美的优势,但我怀疑你有兴趣做些什么。

我的猜测是,根据延迟和您需要执行的实际请求的数量,使用任务在许多(?)情况下会表现更好,但最终会占用更多内存,而并行将在资源使用方面更加一致。当然,异步I / O将比这两个选项中的任何一个都更好地执行,但我知道你不能这样做,因为你使用的是遗留库。所以,不幸的是,无论你选择哪一个,你都会陷入平庸的表现。

真正的解决方案是找出一种方法来实现异步I / O;因为我不知道情况,我认为我不能比这更有帮助。您的程序(读取,线程)将继续执行,内核将等待I / O操作完成(这也称为使用I / O完成端口)。由于线程未处于等待状态,因此运行时可以在较少的线程上执行更多操作,这通常最终会在内核数量和线程数之间建立最佳关系。尽可能多地添加更多线程并不等同于更好的性能(实际上,它通常会损害性能,因为像上下文切换这样的事情)。

然而,在确定问题的最终答案时,这整个答案毫无用处,尽管我希望它会给你一些必要的指导。在分析之前,您不会知道什么表现更好。如果你不同时尝试它们(我应该澄清我的意思是没有LongRunning选项的任务,让调度程序处理线程切换)并对它们进行分析以确定最适合你的特定用例,你卖得很短。

答案 1 :(得分:4)

这两个选项都完全不适合您的情况。

对于不受CPU限制的任务,

TaskCreationOptions.LongRunning当然是更好的选择,因为TPL(Parallel类/扩展)几乎专门用于最大化CPU绑定操作的吞吐量在多个核心(而不是线程)上运行它。

然而,1000个任务是不可接受的数字。他们是否一次全部奔跑并不是问题所在;甚至100个等待同步I / O的线程也是难以为继的情况。正如其中一条评论所暗示的那样,您的应用程序将使用大量内存,并最终花费在上下文切换上的所有时间。 TPL不是为这种规模而设计的。

如果您的操作受I / O限制 - 如果您使用的是Web服务,它们 - 那么异步I / O不仅是正确的解决方案,而且只是 解决方案。如果必须重新构建一些代码(例如,将异步方法添加到最初没有的主要接口),执行,因为I / O完成端口是<在Windows或.NET中只有机制可以正确支持这种特定类型的并发。

我从来没有听说过异步I / O在某种程度上“不是一种选择”的情况。我甚至无法想到这种约束的任何有效用例。如果您无法使用异步I / O,那么这将指示必须修复的严重设计问题, ASAP

答案 2 :(得分:4)

虽然这不是直接比较,但我认为它可能会对您有所帮助。我做了类似于你描述的事情(在我的情况下,我知道在另一端有一个负载均衡的服务器集群服务于REST调用)。我使用Parrallel.ForEach来获得最佳数量的工作线程,如果我还使用以下代码来告诉我的操作系统它可以连接到通常数量的端点,我会得到很好的结果。

    var servicePointManager = System.Net.ServicePointManager.FindServicePoint(Uri);
    servicePointManager.ConnectionLimit = 250;

请注意,您必须为连接到的每个唯一网址拨打一次。