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

时间:2013-09-27 17:06:36

标签: c# multithreading task-parallel-library

我尝试实现的是在集合中的每个值的不同线程中执行方法。我想测量所有任务完成之前的经过时间。我的代码如下:

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;
            }
        }

我期望这里执行Tasker方法10次,但每次都有不同的结果。当我查看结果时,我会看到以下内容:

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

多次执行'j'的Tasker方法。我无法弄清楚为什么该方法不会对集合中的其他字母执行。我在这里缺少什么?

1 个答案:

答案 0 :(得分:1)

我认为问题基本上是您从传递给Task.Factory.StartNew的操作中访问Pair变量。

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

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

你可以这样解决:

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

现在,您正在为foreach的每次迭代创建一个单独的本地范围变量,该变量不会对您有所改变。

顺便说一下,我认为你的代码的另一个逻辑问题是你在外部循环的每次迭代中重用相同的ConcurrentQueue引用,但每次都要更改它。你根本不应该有这条​​线:

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

您应该使用一些更好的方法来管理每个线程组的结果。在这种情况下,您可以使用ConcurrentQueue的ConcurrentQueue ...但我个人改为使用任务参数。在Operation方法中本地创建ConcurrentQueue,并将其作为辅助参数传递给Tasker方法。