启动线程时的不同行为:ParameterizedThreadStart与Anonymous Delegate。为什么这有关系?

时间:2009-12-17 18:10:25

标签: c# multithreading closures

当我运行下面的代码时,输​​出是“DelegateDisplayIt”,通常重复1-4次。我运行这段代码的次数大概是100次,而且输出的次数一直都不是“ParameterizedDisplayIt”。因此,似乎创建并随后启动线程的方式会影响参数的传递方式。使用匿名委托创建新线程时,该参数是对原始变量的引用,但是当使用ParameterizedThreadStart委托创建时,该参数是一个全新的对象?我的假设是否正确?如果是这样,这似乎是线程构造函数的一个奇怪的副作用,没有?

static void Main()
{
    for (int i = 0; i < 10; i++)
    {
        bool flag = false;

        new Thread(delegate() { DelegateDisplayIt(flag); }).Start();

        var parameterizedThread = new Thread(ParameterizedDisplayIt);
        parameterizedThread.Start(flag);

        flag = true;
    }

    Console.ReadKey();
}

private static void DelegateDisplayIt(object flag)
{
    if ((bool)flag)
        Console.WriteLine("DelegateDisplayIt");
}

private static void ParameterizedDisplayIt(object flag)
{
    if ((bool)flag)
        Console.WriteLine("ParameterizedDisplayIt");
}

3 个答案:

答案 0 :(得分:5)

你的假设是正确的。语句parameterizedThread.Start(flag)在调用时复制标志变量。

相比之下,匿名委托在closure中捕获原始变量。在委托执行DelegateDisplayIt方法之前,不会复制该变量。此时,该值可能为true false,具体取决于原始线程在调用循环中的位置。

答案 1 :(得分:4)

flag 是一个布尔变量。它通过值传递给委托。它将永远成为真,因为伪值被复制并发送给代理。

如果您使用匿名委托,则隐式使用closure来访问布尔值。

在.NET中,编译器构造一个匿名类型来保存作为闭包主语的变量的引用( flag ),匿名委托和方法将引用该变量。然后他们将共享变量,因此如果更改变量,它们都会看到变化。

这确实不是一个线程问题,它关于传值和闭包。这是一个decent blog post about closures。按值传递非常简单;如果你需要了解我的建议,我建议你购买一份由Jeffrey Richter提供的CLR Via C#。

答案 2 :(得分:1)

让我们来看第一个案例:

for (int i = 0; i < 10; i++)
{
    bool flag = false;
    new Thread(delegate() { DelegateDisplayIt(flag); }).Start();
    flag = true;
}

这里构造匿名委托时,flag的值为false,但是当DelegateDisplayIt方法执行时,下一行已经将标志设置为true,并且您看到输出显示。这是另一个说明相同概念的例子:

for (int i = 0; i < 5; i++) 
{
    ThreadPool.QueueUserWorkItem(state => Console.WriteLine(i));
}

这将打印五次五次。

现在让我们来看第二种情况:

for (int i = 0; i < 10; i++)
{
    bool flag = false;
    var parameterizedThread = new Thread(ParameterizedDisplayIt);
    parameterizedThread.Start(flag);        
    flag = true;
}

传递给回调的值是变量在调用Start方法时所拥有的值,即false,这就是您在控制台中看不到输出的原因。