foreach标识符和关闭

在下面的两个片段中,第一个是安全还是第二个?

我的意思是每个线程都保证从创建线程的同一循环迭代中调用Foo上的方法?

或者你必须将引用复制到一个新的变量“local”到循环的每个迭代中?

var threads = new List<Thread>();
foreach (Foo f in ListOfFoo)
{      
    Thread thread = new Thread(() => f.DoSomething());
    threads.Add(thread);
    thread.Start();
}

-

var threads = new List<Thread>();
foreach (Foo f in ListOfFoo)
{      
    Foo f2 = f;
    Thread thread = new Thread(() => f2.DoSomething());
    threads.Add(thread);
    thread.Start();
}

更新:正如Jon Skeet的回答所指出的那样,这并没有任何与线程特别相关的东西。


编辑:这一切都在C#5中的变化,改变了变量定义的位置(在编译器的眼中)。 从C#5开始,它们是一样的。


第二个是安全的; 第一个不是。

foreach ,变量在循环之外声明 - 即

Foo f;
while(iterator.MoveNext())
{
     f = iterator.Current;
    // do something with f
}

这意味着关闭范围只有1 f ,线程很可能会感到困惑 - 在某些情况下多次调用该方法,而在其他情况下则不会调用该方法。 你可以在循环中使用第二个变量声明来解决这个问题:

foreach(Foo f in ...) {
    Foo tmp = f;
    // do something with tmp
}

这在每个闭包范围内都有一个单独的tmp ,所以不存在这个问题的风险。

这是一个简单的问题证明:

    static void Main()
    {
        int[] data = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
        foreach (int i in data)
        {
            new Thread(() => Console.WriteLine(i)).Start();
        }
        Console.ReadLine();
    }

输出(随机):

1
3
4
4
5
7
7
8
9
9

添加一个临时变量,它的工作原理:

        foreach (int i in data)
        {
            int j = i;
            new Thread(() => Console.WriteLine(j)).Start();
        }

(每个号码一次,但当然订单不能保证)


Pop Catalin和Marc Gravell的回答是正确的。 我想添加的所有内容都是链接到我关于闭包的文章(其中讨论了Java和C#)。 只是认为它可能会增加一点价值。

编辑:我认为值得给出一个没有线程不可预测性的例子。 这是一个简短但完整的程序,展示了两种方法。 “不良行为”列表打印10次10​​次; “好行动”清单从0到9。

using System;
using System.Collections.Generic;

class Test
{
    static void Main() 
    {
        List<Action> badActions = new List<Action>();
        List<Action> goodActions = new List<Action>();
        for (int i=0; i < 10; i++)
        {
            int copy = i;
            badActions.Add(() => Console.WriteLine(i));
            goodActions.Add(() => Console.WriteLine(copy));
        }
        Console.WriteLine("Bad actions:");
        foreach (Action action in badActions)
        {
            action();
        }
        Console.WriteLine("Good actions:");
        foreach (Action action in goodActions)
        {
            action();
        }
    }
}

您需要使用选项2,在变量变量周围创建闭包时,将使用变量的值,而不是闭包创建时。

C#中匿名方法的实现及其后果(第1部分)

C#中匿名方法的实现及其后果(第2部分)

C#中匿名方法的实现及其后果(第3部分)

编辑:为了说清楚,在C#中,闭包是“ 词法闭包 ”,这意味着它们不捕获变量的值,而是捕获变量本身。 这意味着当为一个变量变量创建一个闭包时,闭包实际上是对该变量的引用,而不是其值的副本。

Edit2:如果有人有兴趣阅读关于编译器内部的内容,可以添加所有博客文章的链接。

链接地址: http://www.djcxy.com/p/51085.html

上一篇: The foreach identifier and closures

下一篇: Is there such a thing as a "Javascript context" in C#