我在構建一個控制檯應用程序,它必須處理一堆文檔。添加AsParallel()調用會導致我的代碼在寫入文件時中斷
保持簡單,其過程是:
- 每年X和Y之間的,查詢數據庫得到的文檔參考列表來處理
- 每個該引用的過程的局部文件
的處理的方法,我認爲,獨立的,應儘快輸入ARG遊戲不同的並行化:
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的建議
[編輯]代碼檢索文件:
/// <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有錯誤,並且每個文檔返回兩次。
這裏不得不潛水我覺得
這可能是因爲我的源枚舉
並行將不會有太大的幫助,因爲I/O。 –
@HenkHolterman:這是可能的。我必須對它進行基準測試。在此之前,我想根據自己的理智和知識解決我的併發問題。 –
@HenkHolterman:解決了我的問題後,我測量了我的程序。使用單個線程處理5000個文檔,耗時35秒。添加AsParallel()方法將過程減少到8秒。這是接近每4分區(我在一個四線程處理器...邏輯)。我沒有提到的是過程文件中的那種工作。它解析文件的內容,對引用列表執行reg搜索,替換爲某個東西,然後將結果發送給WS以便在外部存儲轉換。所以我有一個很重的字符串作業+構造和發送soap消息的延遲。 –