Consider the following code sample, which creates an enumerable collection of integers and processes it in parallel:
using System.Collections.Generic;
using System.Threading.Tasks;
public class Program
{
public static void Main()
{
Parallel.ForEach(CreateItems(100), item => ProcessItem(item));
}
private static IEnumerable<int> CreateItems(int count)
{
for (int i = 0; i < count; i++)
{
yield return i;
}
}
private static void ProcessItem(int item)
{
// Do something
}
}
Is it guaranteed that the worker threads generated by Parallel.ForEach()
each get a different item or is some locking mechanism around incrementation and returning of i
required?
As written it is thread safe but if you comment out the lock(_sync) in AllValues you should be able to verify that it is not thread safe by running it a few times. If you get an InvalidOperationException it proves that it is NOT thread safe.
The foreach operation itself is not thread safe. Say if you run a foreach loop to remove item from forward direction, it'll fail with "Collection was modified; enumeration operation may not execute." exception message.
The Parallel. ForEach method splits the work to be done into multiple tasks, one for each item in the collection. Parallel. ForEach is like the foreach loop in C#, except the foreach loop runs on a single thread and processing take place sequentially, while the Parallel.
The execution of Parallel. Foreach is faster than normal ForEach.
Parallel.ForEach<TSource>
, when TSource
is an IEnumerable<T>
, creates a partitioner for the IEnumerable<T>
that includes its own internal locking mechanism, so you don't need to implement any thread-safety in your iterator.
Whenever a worker thread requests a chunk of items, the partitioner will create an internal enumerator, which:
As you see, the run through the IEnumerable<T>
for the purposes of partitioning is sequential (accessed via a shared lock), and the partitions are processed in parallel.
TPL and PLINQ use the concept of partitioners.
Partitioner is a type, that inherits Partitioner<TSource>
and serves for the splitting the source sequence into a number parts (or partitions). Built-in partitioners were designed to split the source sequence into nonoverlapping partitions.
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