Предположим, что вы ограничиваете параллельность двумя потоками.Есть (как минимум) два возможных способа, которыми Parallel.ForEach
может потенциально работать.Одним из способов может быть запуск двух потоков, и каждому дается половина элементов для завершения.Таким образом, если у вас есть 850 предметов, то в действительности получится, что поток 1 получает первые 425 элементов, а поток 2 получает второй блок из 425 элементов.Теперь оба потока идут на работу.Порядок обработки предметов будет примерно таким: [0, 425, 426, 1, 2, 427, 3, 428, 429, 4, ...].
Это вполне возможно (вероятно, на самом деле) что один из потоков выполнит свою группу элементов намного быстрее, чем другой.
Еще один способ, которым он может работать, - запустить два потока, каждый из которых должен получить элемент из списка, обработать его изатем получите следующий элемент, повторяя до тех пор, пока не останется элементов для обработки.В этом случае порядок обработки элементов будет больше похож на [0, 1, 2, 4, 3, 6, 5, ...].
В первом примере каждому потоку дается блокэлементов для обработки.Во втором случае каждый поток обрабатывает элементы из общего блока до тех пор, пока не останется элементов.
Существуют варианты, но это два основных способа разделения работы между несколькими потоками.Либо дайте каждому свою собственную группу элементов, либо ожидайте, что каждый поток запросит следующий элемент после того, как он закончит обработку.
Parallel.ForEach
реализован первым способом: каждый поток получает свою собственную группуэлементы для обработки.Выполнение этого другим способом потребовало бы дополнительных затрат, поскольку список элементов должен обрабатываться как общая очередь, что приводит к дополнительным издержкам синхронизации.