Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lambda expressions, captured variables and threading

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.

like image 368
ShdNx Avatar asked Oct 10 '10 11:10

ShdNx


3 Answers

  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.
like image 71
Paul Michalik Avatar answered Nov 06 '22 14:11

Paul Michalik


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.

like image 9
Sanjay Manohar Avatar answered Nov 06 '22 16:11

Sanjay Manohar


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);      // value is not changed
      }); 

 // -- OR --

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

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

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

like image 5
Henk Holterman Avatar answered Nov 06 '22 16:11

Henk Holterman