As I discovered that the source IEnumerable is called by PLINQ from multiple threads, it made me thinking: how can it possibly work? E.g. when someone writes code like this:
Enumerable.Range(1,1000).AsParallel()...
the Range iterator must keep its current state in some variable. If this state is accessed from multiple threads, how are race conditions avoided? Here are my findings in order:
1. Enumerable.Range()
returns an IEnumerable<int>
. This enumerable’s GetEnumerator()
will return different enumerators for different threads. This appears to be a built-in feature of enumerables generated from yield return
keyword. Reflector is my witness. Proof link.
Each enumerator, however, is not thread safe and does not have any locking code.
2. AsParallel()
calls GetEnumerator()
only once, and then uses it from multiple threads, so the above protection does not work.
3. Stephen Toub of Microsoft says, that as IEnumerable
is inherently thread unsafe, PLINQ has locking built in.
4. As per Reflector it appears to be implemented in the PartitionedDataSoruce<T>.MoveNext()
method.
So, relax folks. Your enumerator can and will be thread unsafe, PLINQ has its own locks to take care of that.