Search code examples
c#taskexecution

Run a few simple tasks at once seems to not work together


I went to a problem that the output of each tasks is not as real as it should be if we look at the console write order.

using System;
using System.Threading.Tasks;

namespace randomTaskTest
{
    class Program
    {
        public static void foo()
        {
            Random rnd = new Random((int)DateTime.Now.Ticks); // I know that I should not cast long to int

            int target = 3;
            int currentNumber = rnd.Next(1, 100);
            int tryCounter = 0;

            while(currentNumber!=target)
            {
                currentNumber = rnd.Next(1, 100);
                tryCounter++;
            }

            Console.WriteLine(tryCounter);
            rnd = null;
        }

        static void Main(string[] args)
        {
            for (int i = 0; i < 30; i++)
                Task.Run(() => { foo(); });

            Console.ReadKey();
        }
    }
}

I put there a 30 tasks that stay in loop until the Random class will find the correct number in range of <1;100>. In the perfect world the first output entries in console window should be sorted ascending, but it's not.

My way of understanding this code is that when a random number has been found, then it should be written it in console window asap, because the luckiest random instances leaves the loop as first.

The output is something like: 3
18
7
30

instead of
3
7
18
30

etc.

This is impossible to avoid when the Task creation time is longer than the Task execution time, is it? At least I think that this task takes less time to end than the task time to create it.


Solution

  • The Task API is using the ThreadPool (or additional threads) in the background. Which thread exactly is getting executed when and how long depends on how the operating system schedules the threads in the background.

    For example: Let's say you have two cores, and you're running only two tasks. Both tasks are being executed, and both are at 5 trials. Now some system process from the background needs to do some calculation, and therefore the operating system has to pause one of the two tasks that are running in parallel. The other tasks now continues to, lets say, 500 trials and prints them out before the system process that needed the time clears the way for the other task again, which then immediately hits the target and prints out 6.

    This is called a race condition, and you shouldn't rely on something finishing earlier than something else, because the OS is doing so much in the background that you can never say which thread/task is going to be executed at which exact time. It's a frequent source of errors that are hard to find. :-)

    Also, your processor can only execute as many tasks as it has cores at the exact same time. When more tasks are emitted than cores are available, the API either waits for some of them to complete or some are paused and then resumed later. This also leads to irregular processor time for each task.