locked
Threading Issue RRS feed

  • Question

  • Hi, I have a program that spawns threads of in quick succession including a counter of the thread number. The problem I have is that the numbers are not outputting correctly.

    I have a for loop as follows;

    QueueManagement qm = new QueueManagement();
    
          string item = string.Empty;
          //add items to the new Queue
          for (int i = 1; i < 11; i++)
          {
            item = "Queue item " + i.ToString();
            Thread t = new Thread(() => qm.Add(item));
            t.Start();
          }
    

     the QueueManagement class looks like this;

    public class QueueManagement
      {
        public void Add(string item)
        {
          Console.WriteLine(item);
        }
      }
    

    The output i get varies but will be similar to;

    Queue item 2
    Queue item 2
    Queue item 3
    Queue item 3
    Queue item 4
    Queue item 5
    Queue item 6
    Queue item 9
    Queue item 10
    Queue item 10

    Clearly my understanding of Threading is lacking. Can someone please explain?

    Thanks...

    Thursday, March 10, 2011 5:29 PM

Answers

  • The output is correct. The problem lies in using the lambda expression in that way:

      string item = string.Empty;
      //add items to the new Queue
      for (int i = 1; i < 11; i++)
      {
      //imaging, we have the second run: i=2;
      item = "Queue item " + i.ToString();
      //imaging, we have the second run: item="Queue item 2";
      Thread t = new Thread(
        () => 
         //Thread 1 hasn't started yet, but it is using item...
         //Damn, I'm in Thread 1, but item = "QueueItem 2"
         qm.Add(item)
        );
      t.Start();
      }
    
    

     

    To solve the problem, do this:

      string item = string.Empty;
      //add items to the new Queue
      for (int i = 1; i < 11; i++)
      {
      //imaging, we have the second run: i=2;
      item = "Queue item " + i.ToString();
      //imaging, we have the second run: item="Queue item 2";
      var localItem = item;
      Thread t = new Thread(
        () => 
         //Thread 1 hasn't started yet, but it is using item...
         //Damn, I'm in Thread 1, but item = "QueueItem 2"
         //But, hey, I'm happy to use localItem
         //because, localItem is declared within the loop
         //and therefore, localitem is always a new variable
         //That means, finally I have 10 localItems-variables
         //But only one item-variable!
         qm.Add(localItem)
        );
      t.Start();
      }
    
    
    
    Remember: Here you are dealing with VARIABLES, not only with objects. item can be used in many threads and in parallel! localItem will only be "created" for that lambda which is created.
    • Edited by GreatVolk Thursday, March 10, 2011 5:48 PM better expanation
    • Proposed as answer by Serguey123 Thursday, March 10, 2011 6:30 PM
    • Marked as answer by david.newman Monday, March 14, 2011 10:51 AM
    Thursday, March 10, 2011 5:43 PM

All replies

  • The output is correct. The problem lies in using the lambda expression in that way:

      string item = string.Empty;
      //add items to the new Queue
      for (int i = 1; i < 11; i++)
      {
      //imaging, we have the second run: i=2;
      item = "Queue item " + i.ToString();
      //imaging, we have the second run: item="Queue item 2";
      Thread t = new Thread(
        () => 
         //Thread 1 hasn't started yet, but it is using item...
         //Damn, I'm in Thread 1, but item = "QueueItem 2"
         qm.Add(item)
        );
      t.Start();
      }
    
    

     

    To solve the problem, do this:

      string item = string.Empty;
      //add items to the new Queue
      for (int i = 1; i < 11; i++)
      {
      //imaging, we have the second run: i=2;
      item = "Queue item " + i.ToString();
      //imaging, we have the second run: item="Queue item 2";
      var localItem = item;
      Thread t = new Thread(
        () => 
         //Thread 1 hasn't started yet, but it is using item...
         //Damn, I'm in Thread 1, but item = "QueueItem 2"
         //But, hey, I'm happy to use localItem
         //because, localItem is declared within the loop
         //and therefore, localitem is always a new variable
         //That means, finally I have 10 localItems-variables
         //But only one item-variable!
         qm.Add(localItem)
        );
      t.Start();
      }
    
    
    
    Remember: Here you are dealing with VARIABLES, not only with objects. item can be used in many threads and in parallel! localItem will only be "created" for that lambda which is created.
    • Edited by GreatVolk Thursday, March 10, 2011 5:48 PM better expanation
    • Proposed as answer by Serguey123 Thursday, March 10, 2011 6:30 PM
    • Marked as answer by david.newman Monday, March 14, 2011 10:51 AM
    Thursday, March 10, 2011 5:43 PM
  • Thanks for the response - and it has answered my question. However I'd like to know, is this specifically to do with the way that the lambda works??? If I use a ParameterizedThreadStart all is good... e.g.

    item = "Queue item " + i.ToString();
    Thread t = new Thread(new ParameterizedThreadStart(qm.AddObject));
    t.Start(item);
    

    and that is without using the technique you mentioned. I may not get the response in the correct order (because the Threads are not necessarily executed in order), but I do always get the numbers 1 - 10.

    Is this because with ParameterizedThreadStart the "item" is passed to the Start method as a parameter (and therefore correct at that point in time, whereas with the Lambda the value of "item" is not retrieved until the thread actually starts??? I hope that makes sense!!:)(

    Monday, March 14, 2011 9:22 AM
  • That's pretty much it. The lambda is using a "captured variable", which is where the difference comes in.

    Some more info here:

    http://msdn.microsoft.com/en-us/magazine/cc163362.aspx

    Monday, March 14, 2011 9:57 AM
  • item = "Queue item " + i.ToString();
    Thread t = new Thread(new ParameterizedThreadStart(qm.AddObject));
    t.Start(item); // <= object is delivered to function call. variable isn't used after the call. The thread will work without varible item.
    
    //********** lamba ****
    for (int i = 1; i < 11; i++)
    {
     //imaging, we have the second run: i=2;
     item = "Queue item " + i.ToString();
     //imaging, we have the second run: item="Queue item 2";
     Thread t = new Thread(
      () => 
       qm.Add(item); //=> Here, the variable is used, but too late...
      );
     t.Start();
    }
    
    //********** any methos
    static string item;
    static void myAction() {
         qm.Add(item); //=> Here, the variable is used, but too late, because, the action is called later...
    }
    //....
    for (int i = 1; i < 11; i++)
    {
     //imaging, we have the second run: i=2;
     item = "Queue item " + i.ToString();
     //imaging, we have the second run: item="Queue item 2";
     Thread t = new Thread(myAction);
     t.Start();
    }
    

    It's not only lamdas, it may also happen in other methods. Within the methods, if you are using the variables, not the objects. Your ParameterizedThreadStart is delivering the object into the thread, the variable is not touched by the thread.

    Monday, March 14, 2011 10:19 AM
  • OK, thanks for your help.
    Monday, March 14, 2011 10:51 AM