Is it possible to parallelise a loop where the length of the loop is increased within?
List<int> list = new List<int>() { 0, 1 };
for (int i = 0; i < list.Count; i++)
//Parallel.For(0, list.Count, (i) =>
{
Console.WriteLine(list[i]);
if (i == 0) list.Add(2);
}//);
//foreach (int i in list)
//Parallel.ForEach(list, (i) =>
//{
// Console.WriteLine(i);
// if (i == 0) list.Add(2);
//}//);
Console.ReadLine();
In this simple example, the intended output is:
0
1
2
The above code works correctly with a serial 'for', but fails for the serial 'foreach' due to the collection being modified. For both parallelised implementations, the code completes but the output is missing the final '2'.
It isn't valid to change a collection in a for each loop. Basically modifying the list in any way invalidates the enumerator. The following is a quote from the documents for IEnumerator:
An enumerator remains valid as long as the collection remains unchanged. If changes are made to the collection, such as adding, modifying, or deleting elements, the enumerator is irrecoverably invalidated and its behavior is undefined.
For more information take a look at this post. As far as the parallel implementations:
Parallel.ForEach - This is subject to the same IEnumerator issues that a standard for each isParallel.For - This passes the number of loops into the for as a constant, not as a reference. That means when count changes it doesn't change the number of times it will loopA safer pattern is to add, remove, and modify your list elements prior to calling the parallels implementation. Then threads can process these elements. If that can't be done, then determine the number of elements that you will have after the loop, then use an array to store/process these by index. Finally pull any non-null values back into a list. That way you don't have to worry about thread safety with regards to your list (Insert would push other elements forward making your indexes invalid). The following should work:
// EX: might be initialized with a call to the database: "COUNT(id)"
int expectedElements = 10;
if (myList.Count < expectedElements)
for (var idx = myList.Count; idx <= expectedElements; idx++) myList.Add(null);
var elements = myList.ToArray();
System.Threading.Tasks.Parallel.For(0, expectedElements, (idx) =>
{
// "remove" the element
if (idx % 3 == 0) elements[idx] = null;
// "modify" the element
if (idx % 3 == 1) elements[idx] = DifferentElement(idx);
// "add" an element
if (idx % 3 == 2) elements[idx] = GetNewElement(idx);
});
// clear current list, add new elements, remove null values
myList.Clear();
myList.AddRange(elements);
myList.RemoveAll(item => item == null);
Now you can "add", "remove" and "modify" as much as you want and the result is back in a list!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With