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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With