嵌套的 Parallel.For() 循环和文件创建问题



我一直在研究TPL作为快速生成大量文件的方法 - 我在数据库中有大约1000万行,属于患者的事件,我想将其输出到他们自己的文本文件中,位于位置d:\EVENTS\PATIENTID\EVENTID.txt

我正在使用两个嵌套的 Parallel.ForEach 循环 - 外部循环用于检索患者列表,内部循环用于检索患者事件并将其写入文件。

这是我正在使用的代码,目前非常粗糙,因为我只是想让事情正常进行。

DataSet1TableAdapters.GetPatientsTableAdapter ta = new DataSet1TableAdapters.GetPatientsTableAdapter();
List<DataSet1.GetPatientsRow> Pats = ta.GetData().ToList();
List<DataSet1.GetPatientEventsRow> events = null;
string patientDir = null;
System.IO.DirectoryInfo di = new DirectoryInfo(txtAllEventsPath.Text);
di.GetDirectories().AsParallel().ForAll((f) => f.Delete(true));
//get at the patients
Parallel.ForEach(Pats
, new ParallelOptions() { MaxDegreeOfParallelism = 8 }
, patient =>
{
patientDir = "D:\Events\" + patient.patientID.ToString();
//Output directory
Directory.CreateDirectory(patientDir);
events = new DataSet1TableAdapters.GetPatientEventsTableAdapter().GetData(patient.patientID).ToList();

if (Directory.Exists(patientDir))
{
Parallel.ForEach(events.AsEnumerable()
, new ParallelOptions() { MaxDegreeOfParallelism = 8 }
, ev =>
{
List<DataSet1.GetAllEventRow> anEvent = 
new DataSet1TableAdapters.GetAllEventTableAdapter();    
File.WriteAllText(patientDir + "\" + ev.EventID.ToString() + ".txt", ev.EventData);
});
}
});

我生成的代码运行速度非常快,但在几秒钟后产生错误(其中生成了大约 6,000 个文件)。生成的错误是以下两种类型之一:

DirectoryNotFoundException:找不到路径"D:\Events\PATIENTID\EVENTID.txt"的一部分。

每当产生此错误时,目录结构 D:\Events\PATIENTID\ 都存在,因为已在该目录中创建了其他文件。if 条件在进入第二个循环之前检查是否存在 D:\Events\PATIENTID\。

该进程无法访问文件"D:\Events\PATIENTID\EVENTID.txt,因为它正被另一个进程使用。

发生此错误时,有时指示的文件存在或不存在。

因此,任何人都可以就为什么产生这些错误提供任何建议。我也不明白,据我所知,它应该可以工作(而且确实可以工作,一小段时间)。

来自 MSDN:

当需要对集合的每个元素或固定次数的迭代执行相同的独立操作时,请使用并行循环模式。如果循环的步骤不写入其他步骤读取的内存位置或文件,则循环的步骤是独立的。

Parallel.For可以通过执行多线程来加快行的处理速度,但需要注意的是,如果它使用不正确,它将以程序的意外行为结束,就像您上面所说的那样。

出现以下错误的原因:

DirectoryNotFoundException:找不到路径"D:\Events\PATIENTID\EVENTID.txt"的一部分。

可能是一个线程去写入并且目录不存在意味着,而另一个线程创建它。通常,在进行并行处理时,可能会有竞争条件,因为我们在进行多线程处理,如果我们不使用适当的机制,如锁或监视器,那么我们最终会遇到此类问题。

当您进行文件写入时,尝试写入同一文件时多个线程最终会出现错误,即

该进程无法访问文件"D:\Events\PATIENTID\EVENTID.txt,因为它正被另一个进程使用。

由于一个线程已经在写入文件,因此此时其他线程将无法访问该文件以写入该文件。

我建议在这里使用普通循环而不是并行。

最新更新