ParameterizedThreadStart中的捕获变量

5
我有以下的代码,它创建了10个线程,这些线程会向控制台输出消息:
for (int i = 0; i < 10; i++)
{
    {
        Thread thread = new Thread((threadNumber) =>
            {
                for (int j = 0; j < 10; j++)
                {
                    Thread.Sleep(200);
                    Console.WriteLine(string.Format("Thread: {0}, Line: {1}", threadNumber, j));
                }                           
            });
        thread.Start(i);
    }
}

我的理解是,ParameterizedThreadStart会接收一个对象,该对象的引用副本会发送到线程。如果是这种情况,由于我没有在每个循环中制作i的本地副本,所有新线程都将指向相同的内存位置,这意味着某些线程号可能被“跳过”。然而,我已经运行了这个程序(即使对更多的线程/睡眠时间),每个i的值都有自己的线程。有人能解释一下为什么吗?

1个回答

4

您没有应用任何关于“延迟”或“捕获”的内容,也没有创建一个将i包装的匿名函数。

此处的lambda函数没有引用i,其状态完全内部化/包含,因此没有问题:

(threadNumber) =>
{
    for (int j = 0; j < 10; j++)
    {
        Thread.Sleep(200);
        Console.WriteLine(string.Format("Thread: {0}, Line: {1}", threadNumber, j));
    }                           
});

这里的Start调用:
thread.Start(i);

因为i是“值类型”,并且没有被任何匿名函数捕获,所以将其作为值传递(即复制其值)。从这个意义上说,它像任何普通的struct一样传递给任何普通方法(因为这正是发生的事情)。


如果您改为使用i而不是threadNumber编写lambda,则代码如下:

{
    for (int j = 0; j < 10; j++)
    {
        Thread.Sleep(200);
        Console.WriteLine(string.Format("Thread: {0}, Line: {1}", i, j));
    }                           
});

那么你就会遇到麻烦了。在这种情况下,i 指的是原始变量位置,并且将在线程执行时进行评估。这意味着它可能是创建时的当前值(由于处理时间不太可能),或者是 for 循环中稍后设置的值,或者是最后可能的值 10,并且很可能导致数字在迭代之间跳过或共享。


谢谢你详细的解释,Chris。正是“按值复制”这一部分让我困扰了! - Maverick
@Maverick 注意,即使在他给出的“错误”情况下,您仍然可以通过在闭包之前添加一行 var i1 = i 并在使用 i 的任何地方使用 i1 来获得正确的结果。 - lobsterism

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接