Differing behavior when starting a thread: Paramet

2019-01-26 13:26发布

问题:

When I run the code below the output is "DelegateDisplayIt", typically repeated 1-4 times. I've run this code probably 100 times, and not once has the output ever been "ParameterizedDisplayIt". So it seems the manner in which the thread is created and subsequently started affects how the parameter is passed. When creating a new thread with an anonymous delegate the parameter is a reference back to the original variable, but when created with a ParameterizedThreadStart delegate the parameter is an entirely new object? Does my assumption seem correct? If so, this seems an odd side affect of the thread constructor, no?

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");
}

回答1:

Your assumption is correct. The statement parameterizedThread.Start(flag) copies the flag variable at the time of the call.

By contrast, the anonymous delegate captures the original variable in a closure. The variable isn't copied until the delegate executes the DelegateDisplayIt method. At that point, the value may be true or false, depending on where the original thread is in the calling loop.



回答2:

flag is a boolean variable. It is passed by value to the delegate. It will never be true, because the false value is copied and sent to the delegate.

If you use an anonymous delegate, you'll be implicitly using a closure to access the boolean value.

In .NET, the compiler constructs an anonymous type to hold the reference for the variable that is the subject of the closure (flag) which both the anonymous delegate and the method will then reference. They will then share the variable, so if one changes it both will see the change.

This really isn't a threading question, its about pass-by-value and closures. Here's a decent blog post about closures. Pass-by-value is pretty straight forward; if you need to brush up on that I'd suggest buying a copy of CLR Via C# by Jeffrey Richter.



回答3:

Let's take the first case:

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

Here when you construct the anonymous delegate the value of flag is false, but when the DelegateDisplayIt method executes, the flag has already been set to true by the next line, and you see the output displayed. Here's another example that illustrates the same concept:

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

This will print five times five.

Now let's take the second case:

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

the value passed to the callback is the one that the variable posses when you call the Start method, which is false and that's why you never see the output in the console.