Today I was creating a simple application that needed to read from a queue and send XML to a web service. It seemed to me to be a perfect opportunity to create a multi-threaded application. Since I needed to send multiple properties to a method, I would use the Thread class and pass in a lambda expression. Everything seem to be working fine, but I saw some strange things going on; duplicate data was being sent to web service, even though the queue did not include duplicate data. After a bit of debugging, I noticed that my call with the Lambda expression was passing the same data. I did a little research on this issue and found this document, Closing over the loop variable considered harmful. There’s a lot of technical info at this link, so I thought I would create a more simple example that would demonstrate this issue.
In this example I create a list of numbers and display these numbers to the console. There are two individual foreach statements that loops through the numbers. The first foreach does nothing special; it just calls DoWork. All DoWork does is accepts an int and displays the number in the console. Also DoWork has a sleep method to simulate a long running process.
The second foreach also calls DoWork, but calls DoWork on a separate thread. The thread that is created accepts a Lambda, which points to DoWork. It seems everything should work fine, but when you look at the results for the second foreach the data does not look correct.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace Lambda_Threading { class Program { static void Main(string[] args) { Listnumbers = GetData(); foreach (int number in numbers) { DoWork(number); } Console.WriteLine(""); Console.WriteLine("*****************************************"); foreach (int number in numbers) { Thread thread = new Thread(() => DoWork(number)); thread.Start(); } Console.ReadLine(); } public static void DoWork(int number) { Random rand = new Random(); Thread.Sleep(rand.Next(200, 500)); //Simulate Work Console.Write(number + ", "); } public static List GetData() { List numbers = new List (); for (int i = 0; i < 10; i++) { numbers.Add(i); } return numbers; } } }
As you can see the results of the second foreach is not correct. The same number is displayed multiple time. For example 9 is displayed three times and the numbers 0, 1, 2, 5, and 7 is not displayed at all. The numbers are not in order because of the random sleep time in the DoWork method.
To solve this problem, all that is needed is a temp variable to store the number from the for loop.
foreach (int number in numbers) { int tempNumber = number; Thread thread = new Thread(() => DoWork(tempNumber)); thread.Start(); }
Now the results are what I was expecting. The second foreach includes each number once and there are no duplicate numbers. Again, the numbers are not in order because of the random sleep time in the DoWork method. Even if you remove the sleep, the numbers still may not be in order.
Resources: