SED
SED

Reputation: 311

C# Task.Run skips values, is missing data, when doing many iterations

When doing thousands of iterations of the same function when using Task.Run, the end result when tracking how many times a value has been incremented results in the total iterations don't match up with how many times the value has been incremented. Why? How to fix? Suggested alternative ways? My goal is simply to be able to run the same function in a multithreaded environment as fast as possible.

Code example below shows what I am trying now, you should be able to run it as is to see similar results as I am seeing:

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

I would expect to see this every time it is run:

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

I am open to suggestions on how to fix this or alternative ways that are also fast and efficient, but also accurate, to run a multi threaded environment function over and over thousands of times without missing any incremented values.

Upvotes: 3

Views: 424

Answers (1)

TheGeneral
TheGeneral

Reputation: 81513

This is simply not thread safe (and I am sure you have come to that conclusion yourself). You can't expect multiple threads on multiple cores to always have the same value in cache

One way to get the values you expect is to use Interlocked.Increment which acts as a memory barrier eliminating the processor's ability to re-order memory accesses around the instruction and in-turn will make sure the increment operations are atomic

Increments a specified variable and stores the result, as an atomic operation.

Interlocked.Increment(ref someValue);

Output

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

Full Demo Here

Upvotes: 3

Related Questions