views:

181

answers:

3

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  A: 

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.

Darin Dimitrov
In other words, delegates are bound to names in their scopes, not the values? And their parameters are evaluated only when the delegate is actually called, not when the delegate itself is created?
Vladimir Gritsenko
Exactly, you did a great job synthesizing it. My English is very poor :-(
Darin Dimitrov
Considering your reputation, your English is awesome :-)However, other replies suggest this is only true for anonymous delegates. Complicated :-/
Vladimir Gritsenko
@Vladimir - everything Darin says is true. The only thing really missing from this answer is that it doesn't explain *why* the value passed to Thread.Start(object) never changes.
Jeff Sternal
+3  A: 

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.

Will
+4  A: 

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.

Jeff Sternal