2011-12-09 24 views
4

我在構建一個控制檯應用程序,它必須處理一堆文檔。添加AsParallel()調用會導致我的代碼在寫入文件時中斷

保持簡單,其過程是:

  1. 每年X和Y之間的,查詢數據庫得到的文檔參考列表來處理
  2. 每個該引用的過程的局部文件

的處理的方法,我認爲,獨立的,應儘快輸入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有錯誤,並且每個文檔返回兩次。

這裏不得不潛水我覺得

這可能是因爲我的源枚舉

+2

並行將不會有太大的幫助,因爲I/O。 –

+0

@HenkHolterman:這是可能的。我必須對它進行基準測試。在此之前,我想根據自己的理智和知識解決我的併發問題。 –

+1

@HenkHolterman:解決了我的問題後,我測量了我的程序。使用單個線程處理5000個文檔,耗時35秒。添加AsParallel()方法將過程減少到8秒。這是接近每4分區(我在一個四線程處理器...邏輯)。我沒有提到的是過程文件中的那種工作。它解析文件的內容,對引用列表執行reg搜索,替換爲某個東西,然後將結果發送給WS以便在外部存儲轉換。所以我有一個很重的字符串作業+構造和發送soap消息的延遲。 –

回答

1

Document.FindAll((short)year).Documents線程安全嗎?由於第一個版本和第二個版本之間的區別在於第二個(損壞)版本中,此調用同時運行多次。這可能是這個問題的原因。

+0

我想。這是一個簡單的sproc調用(我在我的問題中添加了方法的主體) –

+0

您能否驗證它是否回來了一個完全不同的年份集合,而不是同一年給你兩次?因爲給定你的代碼,很容易想象像GetStoredProcCommand和AddInParameter這樣的東西可能會混淆來自併發調用的參數(如果它不是線程安全的)。 – mquander

+0

看看我的編輯。在我的EnumerateDocuments方法中可能有錯誤...只需要找到什麼;) –

-1

聽起來你試圖寫入同一個文件。在給定的時間只有一個線程/程序可以寫入文件,所以不能使用並行。

如果您是從同一個文件讀取的,那麼您需要以只讀權限打開文件,因爲不要在其上寫入鎖定。

解決此問題的最簡單方法是在File.WriteAllText中放置一個鎖,假設寫入速度很快,並且值得並行化其餘代碼。

+1

這......並沒有真正回答他錯在哪裏的問題,並且鎖定文件I/O既破壞了並行化的整個目的,也隱藏了問題。 – mquander

+0

他的問題是「爲什麼它不工作」。我說明了爲什麼以及如何輕鬆*解決這個問題,並且我也闡述了我的修復的失敗。他正在嘗試做一些無法完成的事情,他需要重新設計他的設計。雖然我的回答不是最好的答案,但這是一個正確的答案。 – Bengie

+0

嗯,我只是簡單地指出,如果他採納了這個建議,他就永遠不會發現實際的錯誤(正如他在他的文章中所解釋的那樣),他仍然會將每個文件寫兩次;除了做兩倍的工作之外,可能還有其他的因素。這種「修復」是危險的;修復錯誤,不要解決它。 – mquander

3

我建議你讓每個任務都將文件數據放入全局隊列中,並讓並行線程從隊列中寫入請求並進行實際的寫入。

無論如何,在單個磁盤上並行寫的性能比寫順序差多了,因爲磁盤需要旋轉尋求下一個寫入位置,所以你只是蹦跳着磁盤尋道之間。順序寫入最好。

+0

+1 - 這種生產者/消費者策略可能是最好的整體方法。 – mquander

+0

@mquander:具有諷刺意味的是,這個建議與抽象意義上的我幾乎完全相同,但對於不理解文件寫入鎖定的人來說更復雜得多。我仍然同意這個設計比我的好,所以我的+1 – Bengie

相關問題