2

What I try to achieve is executing a method in a different thread for each value in a collection. I would like to measure elapsed time until all tasks are completed. My code is as follows:

private ConcurrentQueue<string> Results { get; set; }
public System.Threading.Timer Updater { get; set; }
private Dictionary<int, string> Instances { get; set; }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            this.Instances = new Dictionary<int, string>();

            this.Instances.Add(01, "A");
            this.Instances.Add(02, "B");
            this.Instances.Add(03, "C");
            this.Instances.Add(04, "D");
            this.Instances.Add(05, "E");
            this.Instances.Add(06, "F");
            this.Instances.Add(07, "G");
            this.Instances.Add(08, "H");
            this.Instances.Add(09, "I");
            this.Instances.Add(10, "J");

            this.Updater = 
            new System.Threading.Timer(new TimerCallback(Updater_CallBack), null, 0, 1000);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="State"></param>
        void Updater_CallBack(object State)
        {
            this.Operation();
        }

        private void Operation()
        {
            var Watcher = new System.Diagnostics.Stopwatch();
            Watcher.Restart();

            this.Results = new ConcurrentQueue<string>();

            var Tasks = new System.Threading.Tasks.Task[this.Instances.Count];

            int i = 0;

            foreach (var Pair in this.Instances)
            {
                Tasks[i] = Task.Factory.StartNew(() => { this.Tasker(Pair.Value); });
                i++;
            }

            System.Threading.Tasks.Task.WaitAll(Tasks);

            Watcher.Stop();

            var Text = new StringBuilder();
            foreach (var Result in Results) Text.Append(Result);
            System.IO.File.AppendAllText(@"D:\Tasks.TXT", Watcher.ElapsedMilliseconds.ToString() + "   " + Text.ToString() + Environment.NewLine);           
        }

        private void Tasker(string Id)
        {
            switch (Id)
            {
                case "A": Thread.Sleep(100); this.Results.Enqueue("a"); break;
                case "B": Thread.Sleep(100); this.Results.Enqueue("b"); break;
                case "C": Thread.Sleep(100); this.Results.Enqueue("c"); break;
                case "D": Thread.Sleep(100); this.Results.Enqueue("d"); break;
                case "E": Thread.Sleep(100); this.Results.Enqueue("e"); break;
                case "F": Thread.Sleep(100); this.Results.Enqueue("f"); break;
                case "G": Thread.Sleep(100); this.Results.Enqueue("g"); break;
                case "H": Thread.Sleep(100); this.Results.Enqueue("h"); break;
                case "I": Thread.Sleep(100); this.Results.Enqueue("i"); break;
                case "J": Thread.Sleep(100); this.Results.Enqueue("j"); break;
            }
        }

What I expect here is execution of the Tasker method 10 times but each time for a different result. When I have a look to my results I see following:

200   jjjjjjjjjj
101   jjjjjjgjjj
101   hhhhhhhhjj
101   hjjjjjjjjj
101   jjjjjjjjhj
100   jjjjjjjjjh
101   jjjjjjjjjj

Tasker method is executed for 'j' multiple times. I cannot figure out why the method is not executed for other letters in the collection. What am I missing here?

Demir
  • 1,787
  • 1
  • 29
  • 42
  • 1
    This is the "closure bug", there are many many posts on this all over this site. See [this question](http://stackoverflow.com/questions/8898925/is-there-a-reason-for-cs-reuse-of-the-variable-in-a-foreach/8899347) – Scott Chamberlain Sep 27 '13 at 17:10
  • I wouldn't call it a bug. I'd switch out the foreach with `Tasks = this.Instances.Select(x => (Action)(() => this.Tasker(x.Value))).Select(x => Task.Factory.StartNew(x)).ToArray()`. That's a bit of C# like pseudocode which may compile and run. –  Sep 27 '13 at 17:18
  • @Will It's a bug in the OP's code in that he's unintentionally closing over a variable that is being modified. – Servy Sep 27 '13 at 17:20
  • 1
    The closure itself is not a bug (however there is a breaking change between C# 4 and C# 5 so if you are compileing in Visual Studio 2012 this does not happen), not understanding that it will happen is the bug. – Scott Chamberlain Sep 27 '13 at 17:20
  • Thank you very much @Scott Chamberlain for the link. I got it now. Please consider writing it down as an answer so I can close the question. – Demir Sep 27 '13 at 17:34
  • @Demir don't make a duplicate of a question that was already asked, you can delete your own question if no one has upvoted answers yet. – Scott Chamberlain Sep 27 '13 at 17:35

1 Answers1

1

I think essentially the issue is that you're accessing the Pair variable from within the action you are passing to Task.Factory.StartNew.

foreach (var Pair in this.Instances)
{
    Tasks[i] = Task.Factory.StartNew(() => { this.Tasker(Pair.Value); });
    i++;
}

You have to realize that this means you aren't reading the variable "Pair" right away, you're telling the task factory to queue a thread pool thread. And then when that thread gets started (which could happen after an indeterminate interval) you are accessing a variable in the local scope of that thread, which has already changed because the foreach loop wasn't waiting around for the task to start.

You can fix it like this:

foreach (var Pair in this.Instances)
{
    var currentValue = Pair.Value;
    Tasks[i] = Task.Factory.StartNew(() => { this.Tasker(currentValue); });
    i++;
}

Now you're making a separate locally scoped variable for each iteration of the foreach which shouldn't change on you.

As an aside, I think a separate logical problem with your code is that you are reusing the same ConcurrentQueue reference per iteration of the outer loop, but you are changing it each time. You should not have this line at all:

private ConcurrentQueue<string> Results { get; set; }

You should be using some better way of managing the results of each thread group. You could use a ConcurrentQueue of ConcurrentQueues in this case... but I'd personally instead use task parameters. Create the ConcurrentQueue locally in your Operation method and pass it to the Tasker methods as a secondary parameter.

Trevor Elliott
  • 11,292
  • 11
  • 63
  • 102
  • Thank you @Trevor Elliott for the information. Problem is is quite clear now. By the way this was a just test code to understand the problem in my original code. Original code does not need any return value. Thanks for the suggestion anyway. – Demir Sep 30 '13 at 08:03