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.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 thisDoStuff
method and stored directly in your lambda expression itself, as a closure. The lambda has keeps a copy ofmessage
.In the second case:
message
is sent to theQueue
, 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 want the anonymous functors reference the same state, capture a identifier from common context.
- If you want stateless lambdas (as I would recommend in a concurrent application) use local copies.
精彩评论