也许您没有意识到这一点,但Parallel类中的成员只是对象周围的简单(复杂)包装器Task。如果您想知道,Parallel该类Task使用TaskCreationOptions.None. 但是,MaxDegreeOfParallelism无论将什么创建选项传递给任务对象的构造函数,都会影响这些任务对象。
TaskCreationOptions.LongRunning给底层一个“提示” TaskScheduler,它可能在线程超额订阅时表现更好。超额订阅适用于高延迟的线程,例如 I/O,因为它会将多个线程(是线程,不是任务)分配给单个内核,以便它始终有事可做,而不是等待当线程处于等待状态时完成的操作。在TaskScheduler使用 的情况下ThreadPool,它将在自己的专用线程上运行 LongRunning 任务(每个任务都有一个线程的唯一情况),否则它将正常运行,调度和工作窃取(真的,无论如何你想要什么)
MaxDegreeOfParallelism控制运行的并发操作数。它类似于指定数据将被拆分和处理的最大分区数。如果TaskCreationOptions.LongRunning能够被指定,所有这一切都会限制一次运行的任务数量,类似于将TaskScheduler最大并发级别设置为该值的 a,类似于此示例。
你可能想要Parallel.ForEach. 但是,添加MaxDegreeOfParallelism等于这么大的数字实际上并不能保证同时运行那么多线程,因为任务仍然由ThreadPoolTaskScheduler. 该调度程序将一次运行的线程数尽可能减少,我认为这是两种方法之间的最大区别。您可以编写(并指定)您自己的TaskScheduler模仿最大程度的并行行为,并拥有两全其美,但我怀疑您有兴趣做的事情。
我的猜测是,根据延迟和您需要执行的实际请求的数量,使用任务在许多(?)情况下会表现得更好,尽管最终会使用更多的内存,而并行在资源使用方面会更加一致。当然,异步 I/O 的性能将比这两个选项中的任何一个都好,但我知道你不能这样做,因为你使用的是遗留库。所以,不幸的是,无论你选择哪一个,你都会陷入平庸的表现。
一个真正的解决方案是想办法让异步 I/O 发生。由于我不了解情况,因此我认为没有比这更有帮助的了。您的程序(读取、线程)将继续执行,内核将等待 I/O 操作完成(这也称为使用 I/O 完成端口)。因为线程不处于等待状态,所以运行时可以在更少的线程上做更多的工作,这通常最终会在内核数和线程数之间达到最佳关系。正如我所希望的那样,添加更多线程并不等同于更好的性能(实际上,由于上下文切换之类的原因,它通常会损害性能)。
但是,整个答案对于确定您的问题的最终答案是没有用的,尽管我希望它会给您一些需要的方向。在您对其进行分析之前,您不会知道什么表现更好。如果您不同时尝试它们(我应该澄清我的意思是没有 LongRunning 选项的任务,让调度程序处理线程切换)并分析它们以确定最适合您的特定用例的方法,那么您就是在卖空自己。