C # Task.Run пропускает значения, пропускает данные, когда делает много итераций

При выполнении тысяч итераций одной и той же функции при использовании Task.Run конечный результат при отслеживании того, сколько раз значение было увеличено, приводит к тому, что итоговые итерации не совпадают с тем, сколько раз значение было увеличено. Зачем? Как исправить? Предлагаемые альтернативные способы? Моя цель - просто иметь возможность запускать ту же функцию в многопоточной среде как можно быстрее.

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

taskNumber = 9980, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 9998, nonTaskIterations = 10000
taskNumber = 9998, nonTaskIterations = 10000
taskNumber = 9999, nonTaskIterations = 10000
taskNumber = 9997, nonTaskIterations = 10000
taskNumber = 9999, nonTaskIterations = 10000
taskNumber = 9997, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 9995, nonTaskIterations = 10000
taskNumber = 9994, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 9998, nonTaskIterations = 10000
taskNumber = 9999, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 9999, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
    static void Main(string[] args)
    {
        for (int i = 0; i < 20; i++)
        {
            //iterate many times to see different outputs
            PrimaryFunction();
        }

        Console.WriteLine("why the hell does TaskNumber not always == nonTaskIterations value?");
        Console.ReadLine();
    }

    static void PrimaryFunction()
    {
        long taskNumber = 0;
        int nonTaskIterations = 0;

        for (int i = 0; i < 100; i++)
        {
            List<Task> tasks = new List<Task>();
            for (int j = 0; j < 100; j++)
            {
                nonTaskIterations++;
                tasks.Add(Task.Run(() =>
                {
                    taskNumber = taskNumber + 1;
                    DoWork(taskNumber);
                }));
            }

            Task.WaitAll(tasks.ToArray());
        }

        Console.WriteLine("taskNumber = " + taskNumber + ", nonTaskIterations = " + nonTaskIterations);
    }

    static void DoWork(long i)
    {
        //do work here
        Random random = new Random();
        var x = random.Next(0, 10).ToString();
    }

Я ожидаю увидеть это каждый раз, когда он запускается:

taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000

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

Всего 1 ответ


Это просто не потокобезопасно (и я уверен, что вы сами пришли к такому выводу). Вы не можете ожидать, что несколько потоков на нескольких ядрах всегда будут иметь одинаковое значение в кэше

Один из способов получить ожидаемые значения - использовать Interlocked.Increment, который действует как барьер памяти, устраняя способность процессора переупорядочивать обращения к памяти вокруг инструкции и, в свою очередь, гарантирует, что операции приращения будут атомарными.

Увеличивает указанную переменную и сохраняет результат как элементарную операцию.

Interlocked.Increment(ref someValue);

Выход

taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000
taskNumber = 10000, nonTaskIterations = 10000

Полная демонстрация здесь


Есть идеи?

10000