При использовании TaskFactory StartNew в цикле for нельзя полагаться на значение индекса цикла

При запуске новых задач в цикле for с помощью TaskFactory я передаю индекс текущего цикла в качестве параметра лямбда-функции, которая запускает задачу. Этот индекс используется для выбора элемента из списка и вызова рабочей функции для элемента. Похоже, вы не можете полагаться на то, что значение индекса надежно передается рабочей задаче.

Если вы выполните код, показанный ниже, вывод консоли показывает, что некоторые из рабочих не запускались, а соответствующее число других было запущено как минимум дважды. Это может выглядеть так:

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

Это можно "вылечить", взяв копию значения индекса цикла и передав его в рабочую функцию (это бит закомментирован в функции Manager RunWorkers), и это дает ожидаемый результат:

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

Я хочу понять это поведение, чтобы я мог должным образом защититься от него (я предполагаю, что сделал что-то глупое, а "исправление" только скрывает проблему и на нее нельзя положиться)

Кстати, снятие защиты в функции Manager RunOne может привести к исключению ArgumentOutOfRange, так как индекс слишком велик.

Ниже я включил код для консольного приложения на C# (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 ответов

Другие вопросы по тегам