0

can someone tell me why the below is not producing the correct results? It is giving me 1233 when I expected 0123.

    public static readonly object locker = new object();
    public static List<int> queue = new List<int>();

    public static void calculate(int input)
    {
        Thread.Sleep(1000);
        lock (locker)
        {
            queue.Add(input);
        }
    }

    [TestMethod]
    public void TestT()
    {
        int[] _intList = new int[] { 0, 1, 2, 3 };
        List<Thread> _threadList = new List<Thread>();
        foreach (int num in _intList)
        {
            Thread t = new Thread(() => calculate(num));
            t.Start();
            _threadList.Add(t);
        }

        foreach (Thread t in _threadList) { t.Join(); }

        foreach (var t in queue)
        {
            Console.WriteLine(t);
        }
    }

When I change it to use a copy of the _intList variable instead, I get the correct results of 0123. Can someone tell me why this is happening? Is it being cached somewhere?

        foreach (int num in _intList)
        {
            int testNum = num;
            Thread t = new Thread(() => calculate(testNum));
            t.Start();
            _threadList.Add(t);
        }

1 Answers1

3

When you're passing a variable to a lambda expression it gets captured by the expression. So it's not a copy but that very variable you're getting. This is a common issue with foreach and delayed execution (multithreaded or not), since the foreach continues num is getting it's next value and if it does so because your thread gets to calculate, it's that value that will be calculated.

If you didn't multithread this but instead called the result of the lambda after the foreach what you would see would be 3 3 3 3 instead, in this case you're simply seeing them ofset by one because, most likely, the time it takes to start the thread is about the same as 1 iteration.

When you're making a copy of the variable that variable is declared within the scope of the foreach and it's a new variable each time, it's not getting changed to the next member and that variable is the one getting captured, giving you the correct result. This is the correct way to do this. The result you're getting isn't unexpected but not guaranteed either, you could be getting anything from 0 1 2 3 to 3 3 3 3 with the 1st method, the second method guarantees a correct output.

Ronan Thibaudau
  • 3,413
  • 3
  • 29
  • 78