Parallel.ForEach循环执行就像一个串行循环

时间:2015-06-18 22:14:57

标签: c# tfs parallel-processing invalidoperationexception parallel.foreach

我花了大约8个多小时在网上寻求帮助,但我找不到任何东西,这里就是这样。

我正在使用Team Foundation Server和C#,我正在尝试获取工作项列表并将它们转换为我们绑定到特殊UI的通用对象。工作项目是特定日期的任务,列表大小约为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的构造内部如下所示:

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集合,但我似乎无法使其工作。

编辑:阅读答案的评论以回答此问题。谢谢。

2 个答案:

答案 0 :(得分:1)

  

请帮我弄清楚我在字典方面做错了什么。

抛出异常是因为List<T> 是线程安全的。

你有一个需要修改的共享资源,使用Parallel.ForEach并没有真正帮助,因为你正在将瓶颈移到lock,导致那里的争用,这很可能是为什么你看到性能实际上降级。线程不是一个神奇的解决方案。你需要渴望拥有尽可能多的独立工人,每个工人都能做自己的工作。

相反,您可以尝试使用PLINQ来对内部的可枚举进行分区。由于您实际上想要在集合中投影每个元素,因此可以使用Enumerable.Select

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

为了确定此解决方案是否实际上比顺序迭代更好,请对代码进行基准测试。永远不要假设更多的线程更快。

答案 1 :(得分: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;
});

编辑:将列表更改为数组