System.Threading.Tasks в цикле for - PullRequest
       4

System.Threading.Tasks в цикле for

4 голосов
/ 23 января 2012

Я отрываю волосы из-за следующей проблемы. У меня есть этот фрагмент кода, который просматривает список объектов и создает задачу обработки для каждого из них.

        IList<TileInfo> tiles = tileSource.Schema.GetTilesInView(extent, level);
        List<Task> renderingTasks = new List<Task>();
        foreach (TileInfo info in tiles) {
            renderingTasks.Add(Task.Factory.StartNew(new Action(delegate {
                Console.WriteLine(Task.CurrentId +"Info object"+ info.GetHashCode());
                             }
                         })));
        }

Этот код печатает:

1Info object36963566
2Info object36963566
3Info object36963566
4Info object36963566
5Info object36963566
6Info object36963566
7Info object36963566
8Info object36963566
9Info object36963566
10Info object36963566
11Info object36963566
12Info object36963566
...

Как видите, проблема в том, что все задачи имеют ссылку на один объект!

Почему все задачи используют только один объект из списка?

Спасибо за помощь

Ответы [ 2 ]

5 голосов
/ 23 января 2012

Я постараюсь добавить некоторые подробности через минуту, но речь идет о закрытии по переменной. Измените свой код на:

По сути, вам нужно создать новую переменную внутри цикла, равную единственному объявлению вне внутренней работы цикла.

IList<TileInfo> tiles = tileSource.Schema.GetTilesInView(extent, level);
List<Task> renderingTasks = new List<Task>();

foreach (TileInfo info in tiles) 
{
   TileInfo closingInfo = info;
   renderingTasks.Add(Task.Factory.StartNew(new Action(delegate {
                      Console.WriteLine(Task.CurrentId +"Info object"+ closingInfo.GetHashCode()); }})));
}

Подробнее:

Идентификатор foreach и замыкания

http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx

4 голосов
/ 23 января 2012

object36963566 должен быть последним экземпляром в вашем списке плиток

Это потому, что foreach работает в текущей реализации .Net. Хотя это будет исправлено в будущем.

Вам нужно прочитать Закрытие переменной цикла, которая считается вредной , чтобы понять, как работает foreach в этом случае.

Task.CurrentId +"Info object"+ info.GetHashCode()

info в приведенном выше коде относится к элементу в списке tiles. delegate, который вы создаете, не будет использовать элемент, на который ссылался info, когда он (делегат) был создан. Скорее он будет использовать текущее значение info (значение, на которое указала информация, когда метод / делегат будет фактически запущен), которое, очевидно, указывает на последний элемент списка. Вот почему вы получаете это поведение

...