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

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

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

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

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

要么

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

哪个选项更好,为什么?

注意 :

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

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

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

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

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

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

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

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

这两个选项都完全不适合您的场景。

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

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

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

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

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

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

请注意,您必须为连接到的每个唯一URL调用一次。