Patrick Bader's Blog a blog about software development

9Dec/100

foreach and lambdas in C#

I recently encountered a bug in a C# application I was writing and it took me quite a while to fix. The situation is as follows: I had an array with some kind of data I wanted to be processed by a thread pool. So my first approach was iterating over the array with a foreach loop and passing a lambda to the thread pool as argument. The code looked like this:

1
2
3
4
5
6
7
8
9
object someState;
int items[] = {1, 2, 3, 4, 5};
 
foreach(int item in items)
{
	ThreadPool.QueueUserWorkItem((unused) => {
		System.Console.WriteLine(item);
	});
}

so, what's wrong with this code? Without the thread pool, nothing at all, it runs just fine, but with the thread pool the following text was written to the console:

5
5
5
5
5

When I saw the output, I was like: "what the hell is going wrong here?". The problem with the code above is: lambdas in C# capture their variables by-reference. For each iteration of the loop, a new value will be assigned to the item variable, an since this variable is captured by the lambda by-reference, all WorkItems in the thread pool will be influenced by the assignment of the loop. In my case the loop run completely, before the first WorkItem was executed. So the value of item was always the value which was last assigned by the loop.
A quick fix for this was assigning the loop variable to a local variable in the for loop:

1
2
3
4
5
6
7
8
9
10
object someState;
int items[] = {1, 2, 3, 4, 5};
 
foreach(int item in items)
{
        int localItem = item;
	ThreadPool.QueueUserWorkItem((unused) => {
		System.Console.WriteLine(localItem);
	});
}

so, each lambda references its own local variable. Another solution is to pass the item explicitly to each WorkItem:

1
2
3
4
5
6
7
8
9
10
object someState;
int items[] = {1, 2, 3, 4, 5};
 
foreach(int item in items)
{
	ThreadPool.QueueUserWorkItem((localItem) => {
		System.Console.WriteLine(localItem);
	},
        localItem);
}

The disadvantage here is: You have to make the variable explicitly available in the lambda, by passing it as a parameter.

Side note:
The upcoming C++0x release will also contain lambda expressions but it will allow you to specify whether a variable shall be bound by value or reference.

Comments (0) Trackbacks (0)

No comments yet.


Leave a comment

No trackbacks yet.