Подтвердить что ты не робот

Для цикла выходит за пределы диапазона

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?

4b9b3361

Ответ 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. Я нашел этот вопрос, мой наивный ответ и особенно некоторые другие ответы, полезные для моего личного опыта обучения. Я оставлю свой ответ здесь, потому что это может быть полезно для других.