过去,我使用 ThreadPool.QueueUserWorkItem 从管理器类中生成多个线程。该管理器类订阅这些生成的线程中的事件,该事件在线程工作完成时引发。然后,管理器类可以使用
lock
将输出写入文本文件,以防止任何竞争条件。
现在我正在使用
Parallel.ForEach
来完成这项工作。以线程安全的方式将所有输出写入文本文件的最佳方法是什么?
我的实现的基本轮廓:
public class Directory
{
public string Path;
public Directory(string path)
{
Path = path;
}
public void Scan()
{
Parallel.ForEach(new DirectoryInfo(Path).GetDirectories(),
delegate(DirectoryInfo di)
{
var d = new Directory(di.FullName);
d.Scan();
//Output to text file.
});
}
}
我的做法是:
new Directory(@"c:\blah").Scan();
任何能指引我正确方向的想法都会很棒。我自己有一些,但我正在寻找最佳实践。我已阅读这篇文章,但它不包含任何对我有帮助的解决方案。
使用 EnumerateDirectories (Fx 4) 而不是 GetDirectories。您当前的代码不能很好地并行工作。
其余的取决于您是否需要按顺序输出。
如果您不关心顺序,则可以简单地锁定输出流(使用辅助对象),写入并继续。不需要复杂的事件。
如果你想维持秩序,
将输出推送到队列。当 ForEach 完成时处理队列或启动一个单独的任务(消费者)以尽快写入。这将是典型的生产者/消费者模式。
请注意,通过并行处理,保持目录写入的顺序变得非常困难。
对于初学者,我会将枚举文件的概念与处理文件的概念分开。
也许让你的
Directory
类实现 IEnumerable<FileInfo>
并使用递归、EnumerateDirectories
和 EnumerateFiles
懒惰地枚举所有文件。 (请参阅 http://msdn.microsoft.com/en-us/library/dd997370.aspx)。
现在您可以处理使用
IEnumerable
并处理它的问题,而无需混合代码来递归目录。
创建输出流。枚举
IEnumerable<FileInfo>
并为每个启动 Task
:请参阅 http://msdn.microsoft.com/en-us/library/dd321424.aspx。在该任务中,读取文件并创建输出字符串后,lock() 并写入输出流。
或者,也许更干净一些,启动一个单独的消费者
Task
来进行写入,并使用 BlockingCollection
在生产者和消费者之间传递数据(请参阅 http://msdn.microsoft.com/en-us /library/dd267312.aspx).
当您创建生产者任务时,您可能需要传入选项来限制最大并行度,因为当前任务调度程序在添加线程来完成工作时并不需要磁盘抖动。
另请参阅https://web.archive.org/web/20221208104708/http://reedcopsey.com/2010/03/17/parallelism-in-net-part-14-the- Different-forms-of-任务/ 以及 Reed 在 TPL 上的所有其他博客文章。
另请参阅链接 TPL 和 RX 的努力,例如http://blogs.msdn.com/b/pfxteam/archive/2010/04/04/9990349.aspx这将为在这种情况下的生产和消费提供更清晰的语法。