Для цикла выходит за пределы диапазона
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
MyClass myClass = new MyClass();
myClass.StartTasks();
}
}
class MyClass
{
int[] arr;
public void StartTasks()
{
arr = new int[2];
arr[0] = 100;
arr[1] = 101;
for (int i = 0; i < 2; i++)
{
Task.Factory.StartNew(() => WorkerMethod(arr[i])); // IndexOutOfRangeException: i==2!!!
}
}
void WorkerMethod(int i)
{
}
}
}
Кажется, что я ++ запускается еще раз до завершения итерации цикла. Почему я получаю исключение IndexOutOfRangeException?
Ответы
Ответ 1
Вы закрываете переменную цикла. Когда это время для WorkerMethod
для вызова, i
может иметь значение два, а не значение 0 или 1.
Когда вы используете закрытие, важно понять, что вы не используете значение, которое имеет переменная в данный момент, вы используете эту переменную. Поэтому, если вы создаете lambdas в цикле следующим образом:
for(int i = 0; i < 2; i++) {
actions[i] = () => { Console.WriteLine(i) };
}
а затем выполните действия, все они напечатают "2", так как это значение i
в данный момент.
Представление локальной переменной внутри цикла решит вашу проблему:
for (int i = 0; i < 2; i++)
{
int index = i;
Task.Factory.StartNew(() => WorkerMethod(arr[index]));
}
< Resharper plug > . Еще одна причина попробовать Resharper - это дает много предупреждений которые помогут вам поймать ошибки, подобные этому раньше. "Закрытие по переменной цикла" относится к числу </Resharper plug >
Ответ 2
Причина в том, что вы используете переменную цикла внутри параллельной задачи. Поскольку задачи могут выполняться одновременно, значение переменной цикла может отличаться от значения, которое было при запуске задачи.
Вы запустили задачу внутри цикла. К моменту выполнения задачи для запроса переменной цикла цикл закончился, потому что переменная я теперь находится за точкой остановки.
То есть:
- я = 2, и петля завершается.
- Задача использует переменную я (которая теперь 2)
Вы должны использовать Parallel.For для параллельного выполнения тела цикла. Вот пример использования Parallel.For
Альтернативно, если вы хотите сохранить текущую структуру, вы можете сделать копию я в локальной переменной цикла, а локальная копия цикла сохранит свое значение в параллельной задаче.
например.
for (int i = 0; i < 2; i++)
{
int localIndex = i;
Task.Factory.StartNew(() => WorkerMethod(arr[localIndex]));
}
Ответ 3
Использование foreach
не выбрасывает:
foreach (var i in arr)
{
Task.Factory.StartNew(() => WorkerMethod(i));
}
Но также не работает:
101
101
Он выполняет WorkerMethod
с последней записью в массиве. Почему это хорошо объяснено в других ответах.
Это работает:
Parallel.ForEach(arr,
item => Task.Factory.StartNew(() => WorkerMethod(item))
);
Примечание
На самом деле это мой первый практический опыт работы с System.Threading.Tasks
. Я нашел этот вопрос, мой наивный ответ и особенно некоторые другие ответы, полезные для моего личного опыта обучения. Я оставлю свой ответ здесь, потому что это может быть полезно для других.