源数组不够长。检查 srcIndex 和长度,以及数组的下限

问题描述 投票:0回答:5

我有一个 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);
        }
    }
});
c# multithreading exception
5个回答
33
投票

您正在通过不同的线程访问列表,但列表不是线程安全的:

您可以使用以下方式锁定列表:

lock(listTotalCost)
    listTotalCost.Add(temp.Value);

或者使用并发集合


4
投票

除了使用

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();

3
投票

使用 System.Collections.Concurrent 命名空间中的内容,您可能需要

ConcurrentBag<T>
。请注意,它不保证订购。


1
投票

您可以在此处使用 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(); 

这种方法将为您创建一个集合,而不是并行地一点一点地收集每个元素。顺序会混乱,但这是正常的,在并行处理事情时应该是可以预料到的


0
投票

除了其他修复选项之外,将

listTotalCost
设为
ConcurrentBag
也应该修复它(正如 Jeroen 建议的那样)。问题不在于您要添加的数组中的“源”数组,正如异常可能表明的那样。

Parallel.ForEach 执行后,您可以

.ToList()
.ToArray()
listTotalCost
返回它。

© www.soinside.com 2019 - 2024. All rights reserved.