Когда вы захватываете переменную с помощью лямбды, эта переменная помещается в объект, сгенерированный компилятором, который является общим для внутренней и внешней области видимости. Когда вы делаете это:
for (int i = 0; i < length - 1; i++)
{
taskList.Add(tf.StartNew(() => results[i] = i));
}
Переменная i
является общей для вашего цикла и всех дочерних задач. Существует только один i
, и он изменяется циклом по мере выполнения задач. Это условие состязания, и каждый раз в массиве будут казаться случайные данные, в зависимости от того, как запланированы задания.
Самый простой способ решить эту проблему - создать неизменяемую переменную в теле цикла:
for (int i = 0; i < length; i++) // You can now use length instead of length - 1
{
int innerI = i;
taskList.Add(tf.StartNew(() => results[innerI] = innerI));
}
Теперь для каждой задачи есть отдельная переменная innerI
, и ей присваивается значение i
ровно один раз, и она не изменится.
Представьте себе старый код, преобразованный компилятором в
class Generated1
{
public int i;
}
var context = new Generated1(); // Exactly one copy of i
for (context.i = 0; context.i < length - 1; context.i++)
{
taskList.Add(tf.StartNew(() => results[context.i] = context.i));
}
В то время как новый код, преобразованный компилятором, становится
class Generated2
{
public int innerI;
}
for (int i = 0; i < length - 1; i++)
{
var context = new Generated2(); // New copy of innerI for every loop iteration
context.innerI = i;
taskList.Add(tf.StartNew(() => results[context.innerI] = context.innerI));
}
Относительно того, почему вам пришлось использовать length - 1
: Некоторые из ваших задач не выполнялись, пока цикл не был завершен. В этот момент i == length
, поэтому вы получите IndexOutOfRangeException
при попытке использовать i
в качестве индекса в вашем массиве. Когда вы исправляете состояние гонки с помощью i
, это больше не может происходить.