Нечетный (цикл / распараллеливают / строка / лямбда), поведение в C#

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

Код:

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
5
задан OGHaza 5 May 2014 в 13:29
поделиться

6 ответов

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

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

7
ответ дан 18 December 2019 в 14:42
поделиться

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

string[] source = new string[] {"this", "that", "other"};
foreach (string text in source)
{
    new Thread(t => Console.WriteLine(t)).Start(text);
}
0
ответ дан 18 December 2019 в 14:42
поделиться

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

Эрик Липперт подробно описывает это в своем блоге: часть 1 , часть 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())

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

4
ответ дан 18 December 2019 в 14:42
поделиться

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

2
ответ дан 18 December 2019 в 14:42
поделиться

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

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

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
ответ дан 18 December 2019 в 14:42
поделиться

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

0
ответ дан 18 December 2019 в 14:42
поделиться
Другие вопросы по тегам:

Похожие вопросы: