Как указал @mace, использование ElementAt
имеет проблемы с производительностью. Каждый раз, когда вы вызываете это, итератор начинается с начала MyList
и пропускает n элементов, пока не достигнет нужного индекса. Это становится все хуже, так как позиция индекса становится выше.
Если вам все еще требуется потоковый доступ к MyList
, вы можете уменьшить проблему с производительностью, используя Skip
и Take
. При поиске позиции в MyList
все равно будет некоторое влияние на производительность, но Take
будет гарантировать, что вы получите пакет элементов, как только попадете туда, вместо того, чтобы делать это для каждого элемента.
Я также заметил, что вы используете стиль раздела foreach, но вы делаете это для всего диапазона. В примере ниже я реализовал стиль разбиения с помощью пакетной обработки.
int totalRecords = MyList.Count();
int batchSize = 250;
Parallel.ForEach(Partitioner.Create(0, totalRecords, batchSize), range =>
{
foreach (var thing in MyList.Skip(range.Item1).Take(batchSize))
{
DoStuff(thing);
//logging and stuff...
}
});
Обновление
После прочтения вопроса у вас также могут возникнуть проблемы с использованием слишком большого количества потоков, что, вероятно, является проблемой, связанной с вводом-выводом, т. Е. С сетью, а затем с DB \ disk. Я говорю это так же, как вы говорите, что загрузка процессора незначительна, и это заставляет меня думать, что вы заблокированы на IO, и это становится все хуже.
Если бы оно было чисто до ElementAt
, вы бы все равно увидели высокую загрузку ЦП.
Сконфигурируйте MaxDegreeOfParallelism
, чтобы настроить максимальное количество используемых потоков:
const int BatchSize = 250;
int totalRecords = MyList.Count();
var partitioner = Partitioner.Create(0, totalRecords, BatchSize);
var options = new ParallelOptions { MaxDegreeOfParallelism = 2 };
Parallel.ForEach(partitioner, options, range =>
{
foreach (int thing in MyList.Skip(range.Item1).Take(BatchSize))
{
DoStuff(thing);
//logging and stuff...
}
});