我正在构建一个必须处理大量文档的控制台应用程序。
为了保持简单,过程是:
我认为,流程方法是独立的,只要输入参数不同,就应该并行化:
private static bool ProcessDocument(
DocumentsDataset.DocumentsRow d,
string langCode
)
{
try
{
var htmFileName = d.UniqueDocRef.Trim() + langCode + ".htm";
var htmFullPath = Path.Combine("x:\path", htmFileName;
missingHtmlFile = !File.Exists(htmFullPath);
if (!missingHtmlFile)
{
var html = File.ReadAllText(htmFullPath);
// ProcessHtml is quite long : it use a regex search for a list of reference
// which are other documents, then sends the result to a custom WS
ProcessHtml(ref html);
File.WriteAllText(htmFullPath, html);
}
return true;
}
catch (Exception exc)
{
Trace.TraceError("{0,8}Fail processing {1} : {2}","[FATAL]", d.UniqueDocRef, exc.ToString());
return false;
}
}
为了枚举我的文档,我有这个方法:
private static IEnumerable<DocumentsDataset.DocumentsRow> EnumerateDocuments()
{
return Enumerable.Range(1990, 2020 - 1990).AsParallel().SelectMany(year => {
return Document.FindAll((short)year).Documents;
});
}
Document
是一个包装文档检索的业务类。此方法的输出是一个类型化数据集(我正在返回Documents表)。该方法等待了一年,我确信一份文件不能超过一年(实际上是年份的一部分)。
请注意AsParallel()
在这里使用,但我从来没有遇到过这个问题。
现在,我的主要方法是:
var documents = EnumerateDocuments();
var result = documents.Select(d => {
bool success = true;
foreach (var langCode in new string[] { "-e","-f" })
{
success &= ProcessDocument(d, langCode);
}
return new {
d.UniqueDocRef,
success
};
});
using (var sw = File.CreateText("summary.csv"))
{
sw.WriteLine("Level;UniqueDocRef");
foreach (var item in result)
{
string level;
if (!item.success) level = "[ERROR]";
else level = "[OK]";
sw.WriteLine(
"{0};{1}",
level,
item.UniqueDocRef
);
//sw.WriteLine(item);
}
}
此方法在此表单下按预期工作。但是,如果我替换
var documents = EnumerateDocuments();
通过
var documents = EnumerateDocuments().AsParrallel();
它停止工作,我不明白为什么。
错误正好出现在这里(在我的处理方法中):
File.WriteAllText(htmFullPath, html);
它告诉我该文件已被其他程序打开。
我不明白是什么原因导致我的程序不按预期工作。由于我的documents
变量是IEnumerable
返回的唯一值,为什么我的流程方法会破坏?
thx for advises
[编辑] 检索文档的代码:
/// <summary>
/// Get all documents in data store
/// </summary>
public static DocumentsDS FindAll(short? year)
{
Database db = DatabaseFactory.CreateDatabase(connStringName); // MS Entlib
DbCommand cm = db.GetStoredProcCommand("Document_Select");
if (year.HasValue) db.AddInParameter(cm, "Year", DbType.Int16, year.Value);
string[] tableNames = { "Documents", "Years" };
DocumentsDS ds = new DocumentsDS();
db.LoadDataSet(cm, ds, tableNames);
return ds;
}
[Edit2] 我的问题的可能来源,感谢mquander。如果我写道:
var test = EnumerateDocuments().AsParallel().Select(d => d.UniqueDocRef);
var testGr = test.GroupBy(d => d).Select(d => new { d.Key, Count = d.Count() }).Where(c=>c.Count>1);
var testLst = testGr.ToList();
Console.WriteLine(testLst.Where(x => x.Count == 1).Count());
Console.WriteLine(testLst.Where(x => x.Count > 1).Count());
我得到了这个结果:
0
1758
删除AsParallel会返回相同的输出。
结论:我的EnumerateDocuments有问题,每个文档返回两次。
不得不潜入这里我想
这可能是我在原因中的源枚举
答案 0 :(得分:3)
我建议你让每个任务将文件数据放入一个全局队列,并让一个并行线程从队列中写入请求并进行实际写入。
无论如何,在单个磁盘上并行写入的性能比顺序写入要差得多,因为磁盘需要旋转以寻找下一个写入位置,因此您只是在搜索之间弹出磁盘。最好按顺序执行写操作。
答案 1 :(得分:1)
Document.FindAll((short)year).Documents
线程安全吗?因为第一个和第二个版本之间的区别在于第二个(损坏)版本,所以此调用同时运行多次。这可能是问题的原因。
答案 2 :(得分:-1)
听起来你正在尝试写入同一个文件。只有一个线程/程序可以在给定时间写入文件,因此您不能使用Parallel。
如果您正在读取同一个文件,那么您需要打开只具有读取权限的文件,而不是对其进行写锁定。
解决问题的最简单方法是在File.WriteAllText周围设置一个锁定,假设编写速度很快,并且值得并行化其余代码。