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

非常感谢,我会尽力。 –