进入以下教程:http://www.albahari.com/threading/
他们说以下代码:
for (int i = 0; i < 10; i++)
new Thread (() => Console.Write (i)).Start();
是非确定性的,可以产生以下答案:
0223557799
我认为当使用lambda表达式时,编译器会创建某种匿名类,通过在捕获类中创建类似它们的成员来捕获正在使用的变量。
但是i
是值类型,所以我认为他应该按值复制。
我的错误在哪里?
如果答案将解释闭包如何工作,如何保存特定int的“指针”,在这种特定情况下生成了什么代码,将会非常有用?
答案 0 :(得分:8)
这里的关键点是闭包关闭变量,而不是关闭值。因此,在您关闭它时给定变量的值是无关紧要的。重要的是调用匿名方法时该变量的值。
当您看到编译器将闭包转换为什么时,这很容易看到。它会创造一些与此类似的东西:
public class ClosureClass1
{
public int i;
public void AnonyousMethod1()
{
Console.WriteLine(i);
}
}
static void Main(string[] args)
{
ClosureClass1 closure1 = new ClosureClass1();
for (closure1.i = 0; closure1.i < 10; closure1.i++)
new Thread(closure1.AnonyousMethod1).Start();
}
所以在这里我们可以更清楚地看到发生了什么。该变量有一个副本,该变量现在已被提升为新类的字段,而不是本地变量。现在修改局部变量的任何地方都会修改此实例的字段。我们现在可以看到为什么你的代码打印它的作用。在启动新线程之后,但在它实际执行之前,主线程中的for
循环将返回并递增闭包中的变量。闭包尚未读取的变量。
要产生所需的结果,您需要做的是确保不是让循环的每个迭代都关闭单个变量,而是每个迭代都需要一个它们关闭的变量:
for (int i = 0; i < 10; i++)
{
int copy = i;
new Thread(() => Console.WriteLine(copy));
}
现在copy
变量在关闭后永远不会改变,我们的程序将打印出0-9(尽管以任意顺序,因为线程可以按照OS的要求进行调度)。
答案 1 :(得分:2)
如Albahari
所述,虽然传递参数是值类型,但每个线程都会捕获memory location
,从而导致意外结果。
这种情况正在发生,因为在线程有任何时间开始之前,循环已经改变了i
内的任何值。
为避免这种情况,您应该使用temp
变量作为Albahari
声明,或者仅在您知道变量不会更改时才使用它。
答案 2 :(得分:1)
i
中的<Console.Write(i)
在即将执行该语句时被正确评估。一旦线程完全创建并开始运行并获得该代码,该语句将被执行。到那时,循环向前移动了几次,因此i
可以是任何值。与常规函数不同,闭包可以看到定义它的函数的局部变量(使它们有用的原因,以及用脚射击自己的方式)。