Почему c # не сохраняет контекст для анонимных вызовов делегатов? - PullRequest
5 голосов
/ 13 января 2012

У меня есть следующий метод:

static Random rr = new Random();
static void DoAction(Action a)
{
    ThreadPool.QueueUserWorkItem(par =>
    {
        Thread.Sleep(rr.Next(200));
        a.Invoke();
    });
}

Теперь я вызываю это в цикле for, как это:

for (int i = 0; i < 10; i++)
{
    var x = i;

    DoAction(() =>
    {
        Console.WriteLine(i); // scenario 1
        //Console.WriteLine(x); // scenario 2
    });
}

в сценарии 1 вывод:* в сценарии 2 вывод: 2 6 5 8 4 ... 0 (случайная перестановка от 0 до 9)

Как вы это объясните?Не предполагается ли в c # сохранение переменных (здесь i) для анонимного вызова делегата?

Ответы [ 3 ]

10 голосов
/ 13 января 2012

Проблема здесь в том, что существует одна i переменная и десять экземпляров / копий x. Каждая лямбда получает ссылку на одну переменную i и один из экземпляров x. Каждое значение x записывается только один раз, и, следовательно, каждая лямбда видит одно значение, записанное в значение, на которое оно ссылается.

Переменная i записывается до тех пор, пока не достигнет 10. Ни одна из лямбд не запускается до завершения цикла, поэтому все они видят окончательное значение i, равное 10

Я считаю, что этот пример немного понятнее, если переписать его следующим образом

int i = 0;  // Single i for every iteration of the loop
while (i < 10) { 
  int x = i;  // New x for every iteration of the loop 
  DoAction(() => {
    Console.WriteLine(i);
    Console.WriteLine(x);
  });
  i++;
};
1 голос
/ 13 января 2012

DoAction порождает нить и сразу же возвращается. К тому времени, когда поток выйдет из своего случайного сна, цикл будет завершен, и значение i увеличится до 10. Значение x, с другой стороны, будет захвачено и заморожено * За 1004 * до вызова, поэтому вы получите все значения от 0 до 9 в случайном порядке, в зависимости от того, как долго каждый поток перейдет в спящий режим на основе вашего генератора случайных чисел.

1 голос
/ 13 января 2012

Я думаю, что вы получите тот же результат с Java или любым объектно-ориентированным языком (не уверен, но здесь это кажется логичным).

Область действия i предназначена для всего цикла, а область действия x - для каждого вхождения.

Resharper поможет вам определить проблему такого рода.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...