简体   繁体   English

System.Threading.Tasks.Task在循环中执行奇怪的行为

[英]System.Threading.Tasks.Task executed in a loop strange behavior

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. 我在这里期望执行Tasker方法10次,但是每次都会得到不同的结果。 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. Tasker方法多次执行“ j”。 I cannot figure out why the method is not executed for other letters in the collection. 我无法弄清楚为什么不对集合中的其他字母执行该方法。 What am I missing here? 我在这里想念什么?

I think essentially the issue is that you're accessing the Pair variable from within the action you are passing to Task.Factory.StartNew. 我认为本质上问题是您正在传递给Task.Factory.StartNew的操作中访问Pair变量。

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. 您必须意识到,这意味着您不会立即读取变量“ Pair”,而是告诉任务工厂将线程池线程排队。 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. 然后,当该线程启动时(可能在不确定的时间间隔后发生),您正在访问该线程的本地范围内的变量,该变量已经更改,因为foreach循环没有等待任务开始。

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. 现在,您正在为foreach的每个迭代创建一个单独的局部范围的变量,该变量不会对您造成影响。

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. 顺便说一句,我认为您的代码有一个单独的逻辑问题,就是您在外循环的每次迭代中都重复使用相同的ConcurrentQueue引用,但是每次都在更改它。 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. 在这种情况下,您可以使用ConcurrentQueues的ConcurrentQueue ...但是我个人会使用任务参数。 Create the ConcurrentQueue locally in your Operation method and pass it to the Tasker methods as a secondary parameter. 在您的Operation方法中本地创建ConcurrentQueue,并将其作为辅助参数传递给Tasker方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM