我尝试通过Parallel.ForEach
将处理后的数据添加到BlockingCollection
来处理大量文本文件。
问题是我希望Task
taskWriteMergedFile使用集合并将它们至少每800000行写入结果文件。
我想我无法在迭代中测试集合大小,因为它是并行的,所以我创建了Task
。
在这种情况下,我可以将任务中的while(true)循环转换为EventWaitHandle
吗?
const int MAX_SIZE = 1000000;
static BlockingCollection<string> mergeData;
mergeData = new BlockingCollection<string>(new ConcurrentBag<string>(), MAX_SIZE);
string[] FilePaths = Directory.GetFiles("somepath");
var taskWriteMergedFile = new Task(() =>
{
while ( true )
{
if ( mergeData.Count > 800000)
{
String.Join(System.Environment.NewLine, mergeData.GetConsumingEnumerable());
//Write to file
}
Thread.Sleep(10000);
}
}, TaskCreationOptions.LongRunning);
taskWriteMergedFile.Start();
Parallel.ForEach(FilePaths, FilePath => AddToDataPool(FilePath));
mergeData.CompleteAdding();
答案 0 :(得分:1)
你可能不希望这样做。相反,让您的任务将每行写入收到的文件。如果要将文件大小限制为80,000行,则在写入第80,000行后,关闭当前文件并打开一个新文件。
来想一想,你所拥有的东西是行不通的,因为GetConsumingEnumerable()
在收集被标记为完成添加之前不会停止。会发生的事情是,在队列中有80,000个项目之前,事物将通过睡眠循环,然后它会在String.Join
上阻塞,直到主线程调用CompleteAdding
。有了足够的数据,你就会耗尽内存。
另外,除非你有充分的理由,否则你不应该在这里使用ConcurrentBag
。只需使用BlockingCollection
的默认值ConcurrentQueue
即可。 ConcurrentBag
是一个非常特殊的用途数据结构,其效果不如ConcurrentQueue
。
所以你的任务变成了:
var taskWriteMergedFile = new Task(() =>
{
int recordCount = 0;
foreach (var line in mergeData.GetConsumingEnumerable())
{
outputFile.WriteLine(line);
++recordCount;
if (recordCount == 80,000)
{
// If you want to do something after 80,000 lines, do it here
// and then reset the record count
recordCount = 0;
}
}
}, TaskCreationOptions.LongRunning);
当然,假设您已在其他位置打开输出文件。最好在任务开始时打开输出,并在foreach
退出后关闭它。
另一方面,您可能不希望生产者循环并行。你有:
Parallel.ForEach(FilePaths, FilePath => AddToDataPool(FilePath));
我不确定AddToDataPool
正在做什么,但如果它正在读取文件并将数据写入集合,则会遇到一些问题。首先,磁盘驱动器一次只能做一件事,所以它最终会读取一个文件的一部分,然后是另一个文件的一部分,然后是另一个文件的一部分,等等。为了读取下一个文件的每个块,它必须寻求正确的位置。寻求磁盘头非常昂贵 - 5毫秒或更长。 CPU时间的永恒。除非您正在进行比读取文件花费更长时间的重载处理,否则您最好一次处理一个文件。除非您可以保证输入文件位于不同的物理磁盘上。 。
第二个潜在的问题是,在运行多个线程的情况下,您无法保证将事物写入集合的顺序。当然,这可能不是问题,但是如果您希望单个文件中的所有数据在输出中组合在一起,那么多个线程每个都会在集合中写入多行,就不会发生这种情况。
要记住一些事情。