Lambda Expressions and Threads in C#

Here’s a small program… what do you think the output will be?

public static void Main(string[] args){
    Thread[] t = new Thread[10];
    for (int tNum = 0; tNum < 10; tNum++) {
        t[tNum] = new Thread(() => {
            Thread.Sleep(new Random().Next(20));
            Console.Write(tNum + " ");
        });
        t[tNum].Start();
    }
    // wait for the threads to finish
    for (int tNum = 0; tNum < 10; tNum++){
        t[tNum].Join();
    }
    Console.WriteLine("\nPress a key...");
    Console.Read();
}

If some of it is gobbledegook to you, let me take a moment to explain what’s going on.  Lines 4-7 create a thread using Lambda expressions – we’re essentially putting the code we want to execute in each Thread in-line. Lambda expressions can “see” variables in the scope in which they are defined, hence  we can reference the loop counter tNum inside the Lambda.

So, what did you think the output would be?

When I was writing something that used a similar construct to this I’d have said it should be the numbers 0-9 inclusive in a random order (because the threads are sleeping for a random time), e.g.:

0 1 2 3 6 7 4 5 8 9

I was wrong! Here’s what I just got from running the above code:

1 1 4 8 8 9 8 10 10 10

Huh? Apart from the fact that some numbers are missing and some are repeated, we’ve got 10 as a value – where the for loop specifically has “< 10” as its while clause.

If this baffles you like it did me initially, let me explain!

The way Lambda expressions work mean even though tNum is an int and hence a value type, when it is captured in the Lambda expression, it acts as if it is of Reference type (it’s actually not, behind the scenes, but it’s the easiest way to think about it). This means we’ve fired off a thread with something that behaves as a reference to tNum. As the thread is busy doing something (or in this case pretending to do something by sleeping for a random time), the main thread’s for() loop is busy incrementing the value that tNum holds. This explains why we’ve got random-looking numbers coming out. And the value 10 is explained by the fact that once the for() loop finished, the last increment took tNum to 10, which terminated the loop.

To fix this kind of problem, it helps to use a variable that isn’t going to change. Here’s the fixed version:

public static void Main(string[] args)
{
    Thread[] t = new Thread[10];
    for (int tNum = 0; tNum < 10; tNum++) {
        int n = tNum; // new line
        t[tNum] = new Thread(() => {
            Thread.Sleep(new Random().Next(20));
            Console.Write(n + " "); // changed line
        });
        t[tNum].Start();
    }
    // wait for the threads to finish
    for (int tNum = 0; tNum < 10; tNum++) {
        t[tNum].Join();
    }
    Console.WriteLine("\nPress a key...");
    Console.Read();
}

On line five we’re now taking a copy of tNum, and we’re referencing that instead in the Lambda. Hey presto, we now get expected output.

Leave a Reply

Your email address will not be published. Required fields are marked *