我有以下方法:
static Random rr = new Random();
static void DoAction(Action a)
{
ThreadPool.QueueUserWorkItem(par =>
{
Thread.Sleep(rr.Next(200));
a.Invoke();
});
}
现在我在这样的for循环中调用它:
for (int i = 0; i < 10; i++)
{
var x = i;
DoAction(() =>
{
Console.WriteLine(i); // scenario 1
//Console.WriteLine(x); // scenario 2
});
}
方案1中的输出为:10 10 10 10 ... 10
在方案2中,输出为:2 6 5 8 4 ... 0
(0到9的随机排列)
你怎么解释这个? c#不应该为匿名委托调用保留变量(这里是i
)吗?
答案 0 :(得分:10)
这里的问题是有一个i
变量和十个x
个实例/副本。每个lambda都获得对单个变量i
和x
实例之一的引用。每个x
只写入一次,因此每个lambda都会看到一个写入它引用的值的值。
变量i
被写入,直到达到10.在循环完成之前,所有lambda都没有运行,所以它们都看到i
的最终值为10
如果你按照以下方式重写它,我发现这个例子更清楚了
int i = 0; // Single i for every iteration of the loop
while (i < 10) {
int x = i; // New x for every iteration of the loop
DoAction(() => {
Console.WriteLine(i);
Console.WriteLine(x);
});
i++;
};
答案 1 :(得分:1)
我认为你会得到与java或任何面向对象语言相同的结果(不确定,但这里似乎合乎逻辑)。
i
的范围适用于整个循环,x
的范围适用于每次出现。
Resharper帮助您找到这种问题。
答案 2 :(得分:1)
DoAction
生成线程,然后立即返回。当线程从随机休眠中唤醒时,循环将结束,i
的值将一直提前到10.另一方面,x
的值是在调用之前捕获并冻结,因此您将以随机顺序从0
到9
获取所有值,具体取决于每个线程根据您的随机时间进入休眠状态的时间长度数字生成器。