在C#中循环内使用线程池

4

我对线程不是很了解,但以下代码是否可接受(我更担心在循环内使用线程池):

      string[] filePaths = GetFilePaths();

      foreach (string filePath in filePaths )
      {
        ThreadPool.QueueUserWorkItem(DoStuff, filePath); 
      }

还有其他的方法可以完成这个吗?

编辑:

注意:每次执行DoStuff都会创建多个子线程(约200个)。这些子线程模拟系统的用户,只负责通过TCP接收和发送信息。


1
你能解释一下你的担忧吗?在我看来,它看起来很好。 - Nick
@Henk:使用 .Net 4 进行开发。 - Draco
为什么DoStuff()会创建200个线程?无论如何,它应该也使用TPL。 - H H
@Henk:我正在编写的应用程序需要模拟这个数量的用户来测试系统。 - Draco
1
线程可以为您购买更多的CPU周期。但这不是您在这里所需要的,您需要更多的硬盘。 - Hans Passant
显示剩余3条评论
4个回答

3

当以下这两种情况同时存在时,您当前的代码可能出现问题:

  • 有大量文件
  • 处理文件(DoStuff)需要相当长的时间

线程池的负载平衡能力不足,并会继续创建越来越多的线程,远超过最佳数量。

如果您可以使用Fx4,则使用TPL。

对于早期版本,请重写您的代码以使用更少的线程。


编辑提示,因为您正在使用Fx4:

您最大的收益可能来自于使用 System.Directory.EnumFiles() 来替换 Directory.GetFiles()

简要说明:

var files = System.Directory.EnumerateFiles(...);  // deferred execution

Parallel.ForEach(files, f => DoStuff(f));  // maybe use MaxDegree or CancelationToken

// all files done here

您还可以将此.ForEach包装在一个(单个)try / catch等中。

如果DoStuff()需要并行处理,您也应该使用TPL,可能会传递CancellationToken等。这将使所有并行性都处于单个调度程序的控制下。

您可能需要协助进行微调,但与没有TPL相比,这也将容易得多。


改进这个草图是我们都想要的 :) - Jon

2

这取决于你想要实现什么目标:如果你希望操作在另一个线程中执行,但不介意它们按严格顺序执行,那么可以简单地执行以下操作:

string[] filePaths = GetFilePaths();
ThreadPool.QueueUserWorkItem(DoStuff, filePaths);

foreach放在DoStuff内部。这可能是一种可接受的解决方案,取决于您期望filePaths具有的值(例如,如果所有路径都在同一设备上,则尝试一次性完成不会更快;甚至可能更慢),而且它绝对是最简单的方法。
如果您确实想并行执行它们,那么您应该研究一下任务并行库(仅适用于.NET 4),特别是Parallel.ForEach。由于限制最大并发任务数是一个好主意,这里是一个示例,展示了您可能如何做到这一点:
var options = new ParallelOptions { MaxDegreeOfParallelism = 2 };
Parallel.ForEach(filePaths, options, i=> {
    DoStuff(i);
});

2
你的担心是正确的。 这种情况,根据循环中有多少项,可能会给线程池施加过大的压力。 我认为我在这里是错误的,在这种情况下,项目将被排队,但可能导致大部分线程池被利用,这并不一定会产生最佳性能。
从.NET 4开始,有一些非常简单的替代方法可以使用线程池。 Parallel.ForEach 在这里对你来说是相关的。 这里是.NET 4中新的并行特性链接:

http://galratner.com/blogs/net/archive/2010/04/24/a-quick-lap-around-net-4-0-s-parallel-features.aspx

更新:根据您编辑提到的200个子线程,我建议操作系统线程并不是轻量级对象。它们有与之相关的开销,并且可能会快速抵消并行性带来的任何收益。并行执行某些任务需要考虑有多少工作发生,目标是什么(释放UI、利用所有核心等),以及最终工作是否CPU密集型或IO受限。还有其他因素,但我认为这些非常重要。我建议创建另一个SO问题,描述您尝试使用这种并行性解决的问题,以便您可以获得更具体于您问题的设计建议。

每个线程非常轻量级,它们只是通过TCP连接发送和接收信息。 - Draco
如果它非常轻量级,那么我猜我们谈论的不是同一类型的线程 :-) - Adam Houldsworth

0
为什么不将您的调用封装在一个Action<T>delegate中,并将它们放入循环中的线程安全队列中。然后您可以启动一个(或多个)线程,直到队列中的所有操作都被执行。这样,您就可以控制所使用的线程数量,并且不必担心生成过多的线程。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接