Нечетное (loop / thread / string / lambda) поведение в C # - PullRequest
5 голосов
/ 01 июля 2010

У меня есть фрагмент кода, который, я думал, будет работать из-за замыканий; однако результат доказывает обратное. Что здесь происходит, чтобы он не давал ожидаемый результат (по одному на каждое слово)?

Код:

string[] source = new string[] {"this", "that", "other"};
List<Thread> testThreads = new List<Thread>();
foreach (string text in source)
{
    testThreads.Add(new Thread(() =>
    {
        Console.WriteLine(text);
    }));
}

testThreads.ForEach(t => t.Start())

Выход:

other
other
other

Ответы [ 6 ]

7 голосов
/ 01 июля 2010

Это связано с тем, что замыкания захватывают саму переменную, не оценивая ее, пока она не будет фактически использована.После окончания цикла foreach значение text равно «other», и, как и после окончания цикла, метод вызывается, а во время вызова значение захваченной переменной text равно «other»

Подробнее об этом см. В этом блоге Эрика Липперта .Он объясняет поведение и некоторые причины этого.

4 голосов
/ 01 июля 2010

Это классическая ошибка захвата переменной цикла.Это влияет как на циклы for, так и foreach: при условии типичной конструкции у вас есть единственная переменная на протяжении всего цикла.Когда переменная перехвачена лямбда-выражением или анонимным методом, это сама переменная (не значение во время захвата), которая захватывается.Если вы измените значение переменной и затем выполните делегат, делегат «увидит» это изменение.

Эрик Липперт подробно расскажет об этом в своем блоге: part 1 , part 2 .

Обычное решение - взять копию переменной внутри цикла :

string[] source = new string[] {"this", "that", "other"};
List<Thread> testThreads = new List<Thread>();
foreach (string text in source)
{
    string copy = text;
    testThreads.Add(new Thread(() =>
    {
        Console.WriteLine(copy);
    }));
}

testThreads.ForEach(t => t.Start())

Причина, по которой это работаетчто каждый делегат теперь будет захватывать отдельный «экземпляр» переменной copy.Перехваченная переменная будет той, которая создана для итерации цикла - ей присваивается значение text для этой итерации .И вот, все это работает.

2 голосов
/ 01 июля 2010

Закрытия в C # не фиксируют значение текста во время создания.Поскольку цикл foreach завершает выполнение до выполнения любого из потоков, каждому из них присваивается последнее значение text.

Это можно исправить:

string[] source = new string[] {"this", "that", "other"};
List<Thread> testThreads = new List<Thread>();

foreach (string text in source)
{
    // Capture the text before using it in a closure
    string capturedText = text;

    testThreads.Add(new Thread(() =>
        {
            Console.WriteLine(capturedText);
        }));
}

testThreads.ForEach(t => t.Start());

.этот код «захватывает» значение text внутри каждой итерации цикла for.Это гарантирует, что замыкание получает уникальную ссылку для каждой итерации, а не разделяет одну и ту же ссылку в конце.

0 голосов
/ 01 июля 2010

Закрытия / лямбды не могут правильно связываться с переменными счетчика foreach или loop.Скопируйте значение в другую локальную переменную (не объявленную как переменную foreach или counter), и оно будет работать как положено.

0 голосов
/ 01 июля 2010

Другие объяснили, почему вы столкнулись с этой проблемой.

К счастью, исправление очень просто:

foreach (string text in source)
{
    string textLocal = text; // this is all you need to add
    testThreads.Add(new Thread(() =>
    {
        Console.WriteLine(textLocal); // well, and change this line
    }));
}
0 голосов
/ 01 июля 2010

Причина, по которой это происходит, состоит в том, что к моменту запуска ваших потоков цикл завершился, и значение локальной текстовой переменной равно «other», поэтому при запуске потоков это то, что печатается.Это можно легко исправить:

string[] source = new string[] {"this", "that", "other"};
foreach (string text in source)
{
    new Thread(t => Console.WriteLine(t)).Start(text);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...