views:

81

answers:

3

I know that .NET lambda expressions can capture outer variables. However, I have seen it a lot of times that variables are passed explicitly to the lambda expression as a parameter, and the .NET library also seems to support that (e.g. ThreadPool.QueueUserWorkItem).

My question is that what are the limitations of these captures? How about lambdas that are actually executed on a different thread than the one they were created on (e.g. ThreadPool.QueueUserWorkItem, or Thread), or lambas that act as callbacks (i.e. invoked at a later time)?

Generally, when should I rely on captured variables, and when to use explicit parameters? For example:

public void DoStuff()
{
     string message = GetMessage();

     ThreadPool.QueueUserWorkItem(s => SendMessage(message)); // use captured variable
     // -- OR --
     ThreadPool.QueueUserWorkItem(s =>
          {
               string msg = (string)s;
               SendMessage(msg);
          }, message); // use explicit parameter
}

Thank you!

Update: fixed the second ThreadPool.QueueUserWorkItem example.

+3  A: 

I think in your first example., you mean

QueueUserWorkItem( () => SendMessage(message) );

In your second item, where does the parameter s come from? I think you mean

QueueUserWorkItem( s => {SendMessage((string)s);} , message );

Now, these two both work equivalently, but

  • In the first case: the parameter message is copied from the scope of this DoStuff method and stored directly in your lambda expression itself, as a closure. The lambda has keeps a copy of message.

  • In the second case: message is sent to the Queue, and the queue keeps hold of it (along with the lambda), until the lambda is called. It is passed at the time of running the lambda, to the lambda.

I would argue that the second case is more programmatically flexible, as it could theoretically allow you to later change the value of the message parameter before the lambda is called. However the first method is easier to read and is more immune to side-effects. But in practice, both work.

Sanjay Manohar
You're right, I missed the parameter in the second part of the example. So as I understand, in practice it doesn't really matter which one I use, but you'd recommend me with going with captured variables, because of simplicity (and readability). Thank you very much for your detailed answer!
ShdNx
+3  A: 

For your example (a reference to an immutable string object) it makes absolutely no difference.

And in general, making a copy of a reference is not going to make much difference. It does matter when working with value types:

 double value = 0.1;

 ThreadPool.QueueUserWorkItem( () => value = Process(value)); // use captured variable

 // -- OR --

 ThreadPool.QueueUserWorkItem( () =>
      {
           double val = value;      // use explicit parameter
           val = Process(val);      // v is not changed
      }); 

 // -- OR --

 ThreadPool.QueueUserWorkItem( (v) =>
      {
           double val = (double)v;  // explicit var for casting
           val = Process(val);      // v is not changed
      }, value); 

The first version is not thread-safe, the second and third might be.

The last version use the object state parameter which is a Fx2 (pre-LINQ) feature.

Henk Holterman
I have to admit I find your example code confusing. In the first part, you're using the parameter, but you don't pass it to do the lambda. But then that's not using a captured variable (per my understanding), because that would mean directly using the value variable. Your second example points out how immutable types behave, which is all right, but it doesn't help me understand the difference. Could you please update your answer to clarify this?
ShdNx
@Shnx, you're right, shabby writing. I'll fix it.
Henk Holterman
Thank you, it is clear now. only thing is that you should also update the comments in the code.+1 for mentioning threadsafety (though it only works with immutable types), but I accepted Sanjay Manohar's answer, because it was faster, clear and detailed. Thank you too for your help!
ShdNx
A: 
  1. If you want the anonymous functors reference the same state, capture a identifier from common context.
  2. If you want stateless lambdas (as I would recommend in a concurrent application) use local copies.
paul_71