C#中的奇数(循环/线程/字符串/ lambda)行为

时间:2010-07-01 18:29:08

标签: c# multithreading loops closures

我有一段代码,我认为因为封闭而会起作用;但是,结果证明不是这样。这里发生了什么不能产生预期的输出(每个单词中的一个)?

代码:

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 个答案:

答案 0 :(得分:7)

这与闭包捕获变量本身而不进行评估直到它实际使用的事实有关。在foreach循环结束后,text的值为“other”,并且在循环结束后调用该方法,并在调用时捕获的变量text的值是“其他”

See this blog post from Eric Lippert了解详情。他解释了行为及其背后的一些原因。

答案 1 :(得分:4)

这是捕获循环变量的经典错误。这会影响forforeach循环:假设一个典型的构造,在循环的整个持续时间内有一个变量。当一个变量被一个lambda表达式或一个匿名方法捕获时,它就是捕获的变量本身(不是捕获时的值)。如果更改变量的值然后执行委托,则委托将“看到”该更改。

Eric Lippert在他的博客中详细介绍了它:part 1part 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 :(得分:2)

C#中的闭包不会在创建时捕获文本的值。由于foreach循环在任何线程执行之前完成执行,因此text的最后一个值被赋予每个。{/ p>

这可以解决:

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());

如您所见,此代码在for循环的每次迭代中“捕获”text的值。这可以保证闭包为每次迭代获得唯一的引用,而不是在最后共享相同的引用。

答案 3 :(得分:0)

发生这种情况的原因是,当你开始你的线程时,循环已经完成,文本局部变量的值是“其他”,所以当你启动那些被打印的线程时。这很容易解决:

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

答案 4 :(得分:0)

其他人已经解释了为什么你遇到这个问题。

幸运的是,修复非常简单:

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
    }));
}

答案 5 :(得分:0)

Closures / lambdas无法正确绑定到foreach或循环计数器变量。将值复制到另一个局部变量(未声明为foreach或计数器变量),它将按预期工作。