Source array was not long enough. Check srcIndex and length, and the array's lower bounds
Solution 1
You're accessing the list by different threads:
You could lock the list with:
lock(listTotalCost)
listTotalCost.Add(temp.Value);
Or use Concurrent collections.
Solution 2
Instead of using Parallel.ForEach
you could also use PLINQ (Parallel LINQ), and call ToList()
at the end. It will take care of the ordering and the thread synchronization for you.
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();
Solution 3
you could apply here a async await approach using a select statement. You just need to change your method to return the value. Extract the code in an extra method:
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");
}
});
}
Then perform the select which will be executed in parallel, wait for all the returned tasks, grab the results and filter out the ones which have no value:
List<decimal> listTotalCost = Task.WhenAll(listDates.Select(async x => await DoItAsync(x)))
.Result
.Where(x => x.HasValue)
.Select(x => x.Value)
.ToList();
This approach will create for you a collection instead of collecting each element bit by bit in parallel. The order will be messed up, but this is normal and should be expected when handling things in parallel
Solution 4
Use something from the System.Collections.Concurrent namespace, you probably want ConcurrentBag<T>
. Note that it does not guarantee ordering.
Related videos on Youtube
want_to_be_calm
Updated on June 04, 2022Comments
-
want_to_be_calm almost 2 years
I have a C# list which will be added value in Parallel Foreach. Now it always returns exception System.IndexOutOfRangeException. When I pointed to the listTotalCost, it has the following message
Source array was not long enough. Check srcIndex and length, and the array's lower bounds.
Is it caused by the thread safe problem and any other issue? Here is my code
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); } } });
-
Blue over 4 yearsYou're doing a
Parallel
loop and adding values in a non-thread safe context. Be VERY careful when modifying variables like this. -
Mong Zhu over 4 yearsyou should consider to take one of the thread safe collections may be a ConcurrentBag
-
want_to_be_calm over 4 yearsIs this the possible reason: Both things have to be done atomically. Within your code, it may happen, that two threads are adding a new element at index 5 and both are increasing. As a result you may have one element at index 5 but none at index 6! This scenario isn't very probable, but it MAY happen...how can I avoid it?
-
Lasse V. Karlsen over 4 yearsUse a thread-safe collection or lock access to the list.
-