我有一个 C# 列表,它将在 Parallel Foreach 中添加值。现在它总是返回异常System.IndexOutOfRangeException。当我指向 listTotalCost 时,它显示以下消息
源数组不够长。检查 srcIndex 和长度,以及 数组的下界。
是否是线程安全问题或者其他问题导致的?这是我的代码
List<decimal> listTotalCost = new List<decimal>();
Parallel.ForEach(listDates, dates =>
{
using (DataSet result = calculationMgr.EvaluateFormula(companyID, dates.startDate, dates.endDate, subIndicatorID.Value.ToString(), null, false, null
, (int)Common.Systems.Sustainability.Constants.ApprovalStatuses.Approved
))
{
DataRow dr = result.Tables[0].Rows[0];
//totalPrice = Convert.ToDecimal(dr["Result"]).ToString("#,##0.00");
decimal? temp = Common.Util.TryToConvertToDecimal(dr, "Result");
if (temp != null)
{
//the following line is the error happened
listTotalCost.Add(temp.Value);
}
}
});
您正在通过不同的线程访问列表,但列表不是线程安全的:
您可以使用以下方式锁定列表:
lock(listTotalCost)
listTotalCost.Add(temp.Value);
或者使用并发集合。
除了使用
Parallel.ForEach
,您还可以使用 PLINQ(并行 LINQ),并在最后调用 ToList()
。它将为您处理排序和线程同步。
var listTotalCost = listDates
.AsParallel() // this makes it parallel
.AsOrdered() // optional
.WithDegreeOfParallelism(2) // optional
.Select(date =>
{
using (DataSet result = calculationMgr.EvaluateFormula(companyID,
date.startDate, date.endDate, subIndicatorID.Value.ToString(),
null, false, null,
(int)Common.Systems.Sustainability.Constants.ApprovalStatuses.Approved))
{
DataRow dr = result.Tables[0].Rows[0];
return Common.Util.TryToConvertToDecimal(dr, "Result");
}
})
.Where(v => v != null)
.Select(v => v.Value)
.ToList();
使用 System.Collections.Concurrent 命名空间中的内容,您可能需要
ConcurrentBag<T>
。请注意,它不保证订购。
您可以在此处使用 select 语句应用异步等待方法。您只需要更改返回值的方法即可。在额外的方法中提取代码:
private async Task<decimal?> DoItAsync(yourType dates)
{
return await Task.Run(()=>
{
using (DataSet result = calculationMgr.EvaluateFormula(companyID, dates.startDate, dates.endDate, subIndicatorID.Value.ToString(), null, false, null
, (int)Common.Systems.Sustainability.Constants.ApprovalStatuses.Approved
))
{
DataRow dr = result.Tables[0].Rows[0];
//totalPrice = Convert.ToDecimal(dr["Result"]).ToString("#,##0.00");
return Common.Util.TryToConvertToDecimal(dr, "Result");
}
});
}
然后执行并行执行的select,等待所有返回的任务,抓取结果并过滤掉没有值的:
List<decimal> listTotalCost = Task.WhenAll(listDates.Select(async x => await DoItAsync(x)))
.Result
.Where(x => x.HasValue)
.Select(x => x.Value)
.ToList();
这种方法将为您创建一个集合,而不是并行地一点一点地收集每个元素。顺序会混乱,但这是正常的,在并行处理事情时应该是可以预料到的
除了其他修复选项之外,将
listTotalCost
设为 ConcurrentBag
也应该修复它(正如 Jeroen 建议的那样)。问题不在于您要添加的数组中的“源”数组,正如异常可能表明的那样。
Parallel.ForEach 执行后,您可以
.ToList()
或 .ToArray()
listTotalCost
返回它。