2

我花了大約8個小時以上在網上尋找幫助,我找不到任何東西,這裏就是這樣。Parallel.ForEach循環表現得像一個串行循環

我正在使用Team Foundation Server和C#,我試圖獲取工作項目列表並將它們轉換爲一個通用對象,我們將其綁定到一個特殊的用戶界面。工作項目是特定日期的任務,並且該列表大小約爲30個項目,因此沒有那麼重要。

循環是這樣的:

List<IWorkItemData> workitems = new List<IWorkItemData>(); 
var queryForData = Store.Query(query).Cast<WorkItem>(); 

if (queryForData.Count() == 0) 
    return workitems; 

Parallel.ForEach(queryForData, (wi) => 
{ 
    var temp = wi; 
    lock (workitems) 
    { 
     TFSWorkItemData tfsWorkItem = new TFSWorkItemData(temp); 
     workitems.Add(tfsWorkItem); 
    } 
}); 

TFSWorkItemData的constuctor的內部看起來是這樣的:

public TFSWorkItemData(WorkItem workItem) 
{ 
    this.workItem = workItem; 

    this.Fields = new Dictionary<string, IFieldData>(); 

    // Add Fields 
    foreach (Field field in workItem.Fields) 
    { 
     TFSFieldData fieldData = new TFSFieldData 
     { 
      Value = field.Value, 
      OldValue = field.OriginalValue, 
      ReferenceName = field.ReferenceName, 
      FriendlyName = field.Name, 
      ValueType = field.FieldDefinition.SystemType 
     }; 
     this.Fields.Add(field.ReferenceName, fieldData); 
    } 
} 

因此,需要約90秒,以執行此操作。我知道抓取30個工作項目並不需要那麼長時間,所以這一定是我正在做的事,導致這個過程需要很長時間。我知道鎖是一個性能問題,但是當我刪除它時,我得到一個InvalidOperationException異常,說該集合已被修改。當我查看這個異常的細節時,我能找到的唯一有用的信息是該集合是一個字典。奇怪的是,它並不像我在工作項目中的字段字典被修改的那樣。而且我的班級裏的字典只是被添加進來的,除非我失去了一些東西,否則這不應該是罪魁禍首。

請幫我弄清楚我在字典上做錯了什麼。我曾嘗試將平行foreach循環移至workitem.Fields集合中,但似乎無法使其工作。

編輯:閱讀答案的評論以回答此問題。謝謝。

+1

你鎖定的列表。將項目添加到列表是並行任務所做的唯一事情。但是你鎖定了列表。 _course_它會像串行一樣行事。 –

+0

另外,你不是指'新的TFSWorkItemData(temp)'? –

+0

鎖定列表是防止發生InvalidOperationException的唯一方法。感謝您的快速響應並指出錯誤。 –

回答

0

我發現了另一種可能適用於任何嘗試做類似事情的人的方法。

// collect all of the IDs 
var itemIDs = Store.Query(query).Cast<WorkItem>().Select(wi = wi.Id).ToArray(); 

IWorkItemData[] workitems = new IWorkItemData[itemIDs.Length]; 

// then go through the list, get the complete workitem, create the wrapper, 
// and finally add it to the collection 
System.Threading.Tasks.Parallel.For(0, itemIDs.Length, i => 
{ 
    var workItem = Store.GetWorkItem(itemIDs[i]); 
    var item = new TFSWorkItemData(workItem); 
    workitems[i] = item; 
}); 

編輯:改變列表排列

+0

您的「提供的解決方案」不是線程安全的。您並行追加到「List 」。 –

+0

你是對的,事實並非如此。希望我最近的編輯修復了一些問題。這幾乎是我現在在我的代碼中做的。 –

1

請幫我弄清楚我在字典上做錯了什麼。

拋出異常,因爲List<T>線程安全的。

您有一個需要修改的共享資源,使用Parallel.ForEach不會真的有所幫助,因爲您正在將瓶頸移至lock,導致爭用出現,這很可能是您爲什麼看到性能實際上降級。線程不是一個神奇的解決方案。你需要渴望擁有儘可能多的獨立工人,他們每個人都可以做自己的工作。

相反,您可以嘗試使用PLINQ,它將在內部對您的枚舉進行分區。既然你真的想項目集合中的每個元素,你可以使用Enumerable.Select

var workItems = queryForData.AsParallel().Select(workItem => new TFSWorkItemData(workItem)).ToArray(); 

爲了看看這種解決方案實際上比連續迭代,基準你的代碼更好。永遠不要假設更多的線程更快。

+0

這樣好多了,但仍不能提高性能。我懷疑'TFSWorkItemData'構造函數中真的有那麼多,除非訪問這些字段是從TFS讀取的。 –

+0

@JohnSaunders我同意,這就是爲什麼我說基準測試將是唯一的事實。如果這是一個簡單的小迭代,那也無濟於事。 –

+0

非常感謝,我會盡力。 –