开发者

Lambda expressions, captured variables and threading

开发者 https://www.devze.com 2023-01-19 15:10 出处:网络
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 libra

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.QueueUserWork开发者_JAVA技巧Item).

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.


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.


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.


  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.
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号