2012-08-15 68 views
1

我想在列表中使用Parallel.ForEach,並嘗試使用列表中的每個項目嘗試進行數據庫調用。我正在嘗試記錄每個項目是否有錯誤。只是想在這裏與專家覈對如果我正在做正確的方式。對於這個例子,我使用文件訪問而不是數據庫訪問來模擬I/O。在Parallel.ForEach和Task.Factory.StartNew中記錄每個項目的異常

static ConcurrentQueue<IdAndErrorMessage> queue = new ConcurrentQueue<IdAndErrorMessage>(); 
    private static void RunParallelForEach() 
    { 
     List<int> list = Enumerable.Range(1, 5).ToList<int>(); 
     Console.WriteLine("Start...."); 
     Stopwatch stopWatch = new Stopwatch(); 
     stopWatch.Start(); 
     Parallel.ForEach(list, (tempId) => 
     { 
     string errorMessage = string.Empty; 
     try 
     { 
      ComputeBoundOperationTest(tempId); 
      try 
      { 
       Task[] task = new Task[1] 
       { 
       Task.Factory.StartNew(() => this.contentFactory.ContentFileUpdate(content, fileId)) 
       }; 
      } 
      catch (Exception ex) 
      { 
       this.tableContentFileConversionInfoQueue.Enqueue(new ContentFileConversionInfo(fileId, ex.ToString())); 
      } 
     } 
     catch (Exception ex) 
     { 
      errorMessage = ex.ToString(); 
     } 
     if (queue.SingleOrDefault((IdAndErrorMessageObj) => IdAndErrorMessageObj.Id == tempId) == null) 
     { 
      queue.Enqueue(new IdAndErrorMessage(tempId, errorMessage)); 
     } 
    } 
    ); 
    Console.WriteLine("Stop...."); 
    Console.WriteLine("Total milliseconds :- " + stopWatch.ElapsedMilliseconds.ToString()); 
} 

下面是輔助方法: -

private static byte[] FileAccess(int id) 
{ 
    if (id == 5) 
    { 
     throw new ApplicationException("This is some file access exception"); 
    } 
    return File.ReadAllBytes(Directory.GetFiles(Environment.SystemDirectory).First()); 
      //return File.ReadAllBytes("Files/" + fileName + ".docx"); 
} 

private static void ComputeBoundOperationTest(int tempId) 
{ 
    //Console.WriteLine("Compute-bound operation started for :- " + tempId.ToString()); 
    if (tempId == 4) 
    { 
     throw new ApplicationException("Error thrown for id = 4 from compute-bound operation"); 
    } 
    Thread.Sleep(20); 
} 

private static void EnumerateQueue(ConcurrentQueue<IdAndErrorMessage> queue) 
{ 
    Console.WriteLine("Enumerating the queue items :- "); 
    foreach (var item in queue) 
    { 
     Console.WriteLine(item.Id.ToString() + (!string.IsNullOrWhiteSpace(item.ErrorMessage) ? item.ErrorMessage : "No error")); 
    } 
} 
+0

對Thread.Sleep()的調用是什麼?這是否「神奇地」使它工作? – 2012-08-15 18:12:10

+0

除了實際答案之外,這段代碼還是有點混亂。怎麼了魔術數字4?爲什麼嵌套try塊?爲什麼忽略所有例外? – Ankush 2012-08-15 18:44:40

+0

@Ankush - 這個數字只是爲了模擬我們可能遇到的一個項目異常。 – 2012-08-15 18:54:32

回答

2

沒有理由這樣做:

/*Below task is I/O bound - so do this Async.*/ 
Task[] task = new Task[1] 
{ 
    Task.Factory.StartNew(() => FileAccess(tempId)) 
}; 
Task.WaitAll(task); 

通過在一個單獨的任務調度一下,然後馬上就可以等待,你只是綁定更多的線程。你最好把這個作爲:

/*Below task is I/O bound - but just call it.*/ 
FileAccess(tempId); 

話雖這麼說,因爲你正在做的每項目一個記錄值(異常或成功),你可能要考慮寫入的方法該然後把整個事情稱爲PLINQ查詢。

例如,如果你寫的這個成處理的try/catch(不帶螺紋),並返回「登錄串」的方法,即:

string ProcessItem(int id) { // ... 

你可以寫整個操作:

var results = theIDs.AsParallel().Select(id => ProcessItem(id)); 
+0

讓我備份 - 我做「Task.WaitAll」的唯一原因是在FileAccess方法期間捕獲任何異常。是否有更好的異常處理方法? – 2012-08-15 19:05:27

+0

@AshishGupta您可以使用'task.Wait()' - 但是如果您按照我的建議序列化ti,則不必 - 異常會直接在該線程中引發 – 2012-08-15 21:07:24

+0

請查看我更新的問題。在每次迭代中,我嘗試執行計算綁定任務和數據庫調用(此處爲I/O模擬的文件訪問)任務。我一直認爲通過Async的I/O總是比以同步方式做的更好。但是,您需要等待如果您希望完成I/O操作,則需要捕獲異常。此外,有效地建議以下Parallel.ForEach(list => {ComputeBoundOperationTest(id); FileAccess(id)})。所以,現在的問題是如果你需要處理異常 - 你需要等待,你會綁定更多的線程?最佳做法是什麼? – 2012-08-16 00:36:37

0

Parallel.ForEach同時運行動作/ func達到一定數量的同時發生的事件。如果這些迭代中的每一個所做的都不是固有的彼此獨立的,那麼你沒有獲得任何性能收益。而且,可能會通過引入昂貴的上下文切換和爭用來降低性能。你說你想做一個「數據庫調用」並用文件操作來模擬它。如果每次迭代使用相同的資源(例如數據庫表中的同一行;或者嘗試寫入同一位置的同一文件),那麼它們並不會真正並行運行。一次只會有一個正在運行,其他人將只是「等待」以獲取資源 - 不必要地讓代碼變得複雜。

你還沒有詳細說明你想要爲每個迭代做什麼;但是當我遇到類似於其他程序員的情況時,他們幾乎總是沒有真正並行的做事,他們只是經歷了並且用Parallel.ForEach代替foreach s,希望神奇地獲得性能或神奇地使用多重-CPU/Core處理器。

+0

請參閱更新問題。在每次迭代中,我嘗試執行一個獨立的計算綁定任務和一個獨立數據庫(用於模擬此處的文件訪問)任務。否 - 數據庫調用是「不」使用相同的資源。他們是獨立的。我試圖並行化計算綁定任務,但也使用Task.Factory.StartNew來執行數據庫調用異步。不過,我也在看上面的裏德的回答,並會試一試。 – 2012-08-15 23:58:08

1

您可能想要從線程代碼中刪除Console.WriteLine。原因是每個Windows應用程序只能有一個控制檯。所以如果兩個或多個線程要並行寫入控制檯,就必須等待。

取代您的自定義錯誤隊列,您可能希望看到.NET 4's Aggregate Exception並捕獲並相應地處理異常。 InnerExceptions propery將爲您提供必要的例外清單。更多here

而且一個通用代碼審查評論,請不要使用像4這樣的神奇數字在if (tempId == 4)而是定義一些可以告訴4代表什麼的const。例如if (tempId == Error.FileMissing)

+0

我知道一個人不應該硬編碼一個數字。但是,這只是爲了測試。在真實世界的應用程序中,你不會這樣做。 – 2012-08-16 00:02:30