Parallel.ForEach 循环的执行方式类似于串行循环
Parallel.ForEach loop is performing like a serial loop
我已经花了大约 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 说集合已被修改。当我查看此异常的详细信息时,我能找到的唯一有用信息是该集合是一本字典。奇怪的是,在我看来,工作项中的字段字典根本没有被修改。我的 class 中的字典只是被添加到,除非我遗漏了什么,否则这不应该是罪魁祸首。
请帮我找出我在字典方面做错了什么。我确实尝试将并行 foreach 循环移动到 workitem.Fields 集合,但我似乎无法让它工作。
编辑:阅读 Answer 的评论以获得此问题的答案。谢谢。
Please help me figure out what I'm doing wrong regarding the dictionaries.
抛出异常是因为List<T>
不是线程安全的。
您有一个需要修改的共享资源,使用 Parallel.ForEach
并没有多大帮助,因为您正在将瓶颈移至 lock
,导致那里的争用,即可能是为什么您看到性能实际上 下降 。线程不是一个神奇的解决方案。你需要渴望有尽可能多的独立工作者,每个人都可以做自己的工作。
相反,您可以尝试使用 PLINQ
,这将在内部对您的枚举进行分区。由于您实际上想要投影集合中的每个元素,因此可以使用 Enumerable.Select
:
var workItems = queryForData.AsParallel().Select(workItem => new TFSWorkItemData(workItem)).ToArray();
为了确定此解决方案是否真的比顺序迭代更好,请对您的代码进行基准测试。永远不要假设线程越多速度越快。
我发现了另一种可能适用于尝试做类似事情的人的方法。
// 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;
});
编辑:将列表更改为数组
我已经花了大约 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 说集合已被修改。当我查看此异常的详细信息时,我能找到的唯一有用信息是该集合是一本字典。奇怪的是,在我看来,工作项中的字段字典根本没有被修改。我的 class 中的字典只是被添加到,除非我遗漏了什么,否则这不应该是罪魁祸首。
请帮我找出我在字典方面做错了什么。我确实尝试将并行 foreach 循环移动到 workitem.Fields 集合,但我似乎无法让它工作。
编辑:阅读 Answer 的评论以获得此问题的答案。谢谢。
Please help me figure out what I'm doing wrong regarding the dictionaries.
抛出异常是因为List<T>
不是线程安全的。
您有一个需要修改的共享资源,使用 Parallel.ForEach
并没有多大帮助,因为您正在将瓶颈移至 lock
,导致那里的争用,即可能是为什么您看到性能实际上 下降 。线程不是一个神奇的解决方案。你需要渴望有尽可能多的独立工作者,每个人都可以做自己的工作。
相反,您可以尝试使用 PLINQ
,这将在内部对您的枚举进行分区。由于您实际上想要投影集合中的每个元素,因此可以使用 Enumerable.Select
:
var workItems = queryForData.AsParallel().Select(workItem => new TFSWorkItemData(workItem)).ToArray();
为了确定此解决方案是否真的比顺序迭代更好,请对您的代码进行基准测试。永远不要假设线程越多速度越快。
我发现了另一种可能适用于尝试做类似事情的人的方法。
// 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;
});
编辑:将列表更改为数组