在for循环中使用TaskFactory StartNew时,不能依赖循环索引值

时间:2015-05-12 18:09:50

标签: c# lambda taskfactory

当使用TaskFactory在for循环中启动新任务时,我将当前循环索引作为参数传递给启动Task的lambda函数。 该索引用于从List中选择项目并调用该项目的worker函数。看来你不能依赖索引的值可靠地传递给worker Task。

如果执行下面的代码,控制台输出会显示某些工作人员没有启动,并且至少两次启动了匹配数量的其他工作人员。它可能看起来像这样:

Results:
        Started: 7, Completed: 7, Already Started: 2

这可以通过获取循环索引值的副本并将其传递给worker函数(它是在Manager RunWorkers函数中注释掉的位)来“固化”,并且这给出了预期的结果:

Results:
        Started: 10, Completed: 10, Already Started: 0

我希望了解这种行为,以便我可以正确地防范它(我假设我做了一些愚蠢的事情,'修复'只隐藏了问题,不能依赖它)

BTW - 删除Manager RunOne函数中的保护可能会导致ArgumentOutOfRange异常,因为索引太大。

我已经为下面的C#consol应用程序(Visual Studio 2013)提供了代码,首先是Worker

namespace ThreadingTest
{
    public class Worker
    {
        public bool hasStarted = false;
        public bool hasCompleted = false;
        public bool hasAlreadyStarted = false;

        public readonly int index;

        private double value;

        public Worker(int _index)
        {
            index = _index;
        }

        public void workSocksOff()
        {
            if (hasStarted)
            {
                hasAlreadyStarted = true;
                return;
            }
            hasStarted = true;
            // Do real work
            for (int i=0; i<10000000; ++i)
            {
                value = Math.Sqrt(i);
            }
            hasCompleted = true;
        }

    }
}

然后是经理

namespace ThreadingTest
{
    public class Manager
    {
        public List<Worker> Workers = new List<Worker>();
        private Object taskLock = new Object();

        public int TaskCount { get; set; }

        public void RunTest()
        {
            AddWorkers();
            RunWorkers();        
        }

        private void RunWorkers()
        {
            TaskCount = 0;
            TaskFactory taskFactory = new TaskFactory(TaskCreationOptions.LongRunning, TaskContinuationOptions.None);
            Task[] taskPool = new Task[Workers.Count];
            for (int i=0; i<Workers.Count; ++i)
            {
                //int why = i;
                //taskPool[i] = taskFactory.StartNew(() => this.RunOne(why))

                taskPool[i] = taskFactory.StartNew(() => this.RunOne(i))
                    .ContinueWith( (antecedant) =>
                    {
                        lock (taskLock) { TaskCount += 1; }
                    }
                    );
            }
            Task.WaitAll(taskPool);
        }

        private void RunOne(int index)
        {
            if (index >= Workers.Count)
                return;
            Workers[index].workSocksOff();
        }

        private void AddWorkers()
        {
            for (var i = 0; i < 10; ++i)
                Workers.Add(new Worker(i));
        }
    }
}

最后程序本身

namespace ThreadingTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Manager manager = new Manager();

            manager.RunTest();

            int started = 0, completed = 0, alreadyStarted = 0;

            foreach (Worker w in manager.Workers)
            {
                if (w.hasStarted) started++;
                if (w.hasCompleted) completed++;
                if (w.hasAlreadyStarted) alreadyStarted++;
            }

            Console.WriteLine("Results: ");
            Console.WriteLine("\tStarted: {0}, Completed: {1}, Already Started: {2}", started, completed, alreadyStarted);
            Console.ReadKey();
        }

    }
}

0 个答案:

没有答案