.NET 4.0: пожалуйста, помогите мне с этой задачей - PullRequest
0 голосов
/ 23 октября 2011

Простая копия макарон здесь:

static void Main(string[] args)
{
    List<Task> Tasks = new List<Task>();

    Random r = new Random();

    for (int o = 0; o < 5; o++)
        Tasks.Add(Task.Factory.StartNew(() => { int i = r.Next(0, 3000); Thread.Sleep(i); Console.WriteLine("{0}: {1}", o, i); }));

    Task.WaitAll(Tasks.ToArray());

    Console.Read();
}

Когда вы запустите это, вы получите что-то вроде этого:

5: 98
5: 198
5: 658
5: 1149
5: 1300

Что я не понимаю по этому поводу? Запись каждой итерации o показывает как 5 для всех потоков, когда я ожидаю увидеть числа от 0 до 4 в случайном порядке.

Я попытался использовать реальный метод вместо анонимного, и он делает то же самое. Чего мне не хватает?

Редактировать: Я только что нашел проблему с моим самым первым постом и отредактировал вопрос, так что извините, если вы ответили о проблеме неправильного заказа. Однако мне любопытно, почему o не пишет правильно.

Ответы [ 3 ]

3 голосов
/ 23 октября 2011
() => 
 { 
   int i = r.Next(0, 3000); 
   Thread.Sleep(i); 
   Console.WriteLine("{0}: {1}", o, i); 
 })

Вы закрываете переменную цикла o с делегатом, который вы используете для своей задачи - к тому времени, когда он будет выполнен, ваш цикл завершится, и вы получите только конечное значение 5 дляo.Помните, что вы создаете замыкание по переменной цикла , а не по ее текущему значению - значение оценивается только при выполнении делегата после запуска задачи.

Вместо этого вам нужно создать локальную копию переменной цикла, которую затем можно безопасно использовать:

for (int o = 0; o < 5; o++)
{
   int localO = o;
   Tasks.Add(Task.Factory.StartNew(() => { int i = r.Next(0, 3000); Thread.Sleep(i); Console.WriteLine("{0}: {1}", localO, i); }));
}
2 голосов
/ 23 октября 2011

Здесь есть как минимум две проблемы.

Проблема с o, имеющим значение 5 на каждой итерации, является одной из тех "ошибок" лексических замыканий.Если вы хотите, чтобы o захватил его текущее значение, вы должны создать переменную с локальной областью в цикле и использовать ее в своей лямбде, например:

for (int o = 0; o < 5; ++o)
{
    int localO = o;
    // now use "localO" in your lambda ...
}

Также Random не является потокобезопасным.Использование одного и того же экземпляра Random одновременно в нескольких потоках может повредить его состояние и дать вам неожиданные результаты.

1 голос
/ 23 октября 2011

Я думаю, что вы предполагаете, что задачи выполняются в том порядке, в котором они были созданы, и TPL не дает таких гарантий ...

Что касается параметра 'o', всегда печатающего как 5, то есть потому, что он является локальной переменной в родительской области действия анонимной функции, следовательно, когда печать фактически выполняется, его значение равно 5, потому что цикл завершен (сравните чтобы «я» находилось в пределах анонимной функции)

...