Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why TaskFactory.StartNew Task is not starting immediately?

As per the MSDN Documentation TaskFactory.StartNew it Creates and starts a Task. So, for the below code sample

class Program
{
    public static void Main()
    {
        var t =Task.Factory.StartNew(
                () => SomeLongRunningCalculation(10, Display)
            );
        var t1 = Task.Factory.StartNew(
                () => SomeLongRunningCalculation(20, Display)
            );
        Console.WriteLine("Invoked tasks");
        Task.WaitAll(t, t1);
        Console.ReadLine();
    }

    public static void Display(int value)
    {
        Console.WriteLine(value);
    }

    public static void SomeLongRunningCalculation(int j, Action<int> callBack)
    {
        Console.WriteLine("Invoking calculation for {0}", j);
        System.Threading.Thread.Sleep(1000);
        if (callBack != null)
        {
            callBack(j + 1);
        }
    }
}      

My expected output was

Invoking calculation for 10
Invoking calculation for 20
Invoked tasks
11 
21

But, it is displaying

Invoked tasks
Invoking calculation for 20
Invoking calculation for 10
21
11

I would like to learn

  1. Why the tasks are not running immediately after StartNew?
  2. What should I do, to get the output in the expected format?
like image 586
Ramesh Avatar asked Apr 22 '12 07:04

Ramesh


2 Answers

This is a very likely outcome on a machine with a single core cpu. Or possible on one with a multi-core cpu and it is also busy doing something else.

Creating a task or a thread only sets up a logical operating system structure that allows code to run. The operating system scheduler does not immediately start executing it if the cores are busy, the thread has to compete with all the other threads that are running on the machine. A typical Windows session has a thousand or so. 64 times per second, the kernel generates an interrupt and the scheduler re-evaluates what's going on to see if another thread should get a turn. Any threads that are not blocking (waiting for some other thread to get a job done like read a file or network packet) are eligible to run and the scheduler picks the one with with the highest priority. Some additional code in the scheduler tinkers with the priority values to ensure that all threads get a chance.

Chance is the key word here. Thread scheduling is non-deterministic.

Note how I never said anything about the Thread or Task or ThreadPool class. They don't have the power to do much of anything about the way the operating system schedules threads. All that's possible is prevent a thread from running, the job of the thread pool scheduler.

Priority matters, you can tinker with the Thread.Priority or Task.Priority property to affect outcome. But no slamdunk, the operating system scheduler constantly adjusts a threads priority from the base priority you set with that property. You cannot prevent a thread from ever running by having another one with a higher priority for example.

Hoping that threads run in a predictable order causes the worst kind of bug, a threading race bug. Second worst is deadlock. They are extremely hard to debug because they depend on timing and available machine resources and load. You can only ensure that you get a particular order by writing code that explicitly takes care of it. Which you do by using a threading primitive like Mutex or the lock keyword. Notable too is that when you try to add such code to your snippet then you'll end up with a program that no longer has concurrency. Or in other words, you'll end up not having any use for a Task anymore. A thread or task will only be useful if you can afford for it to run at unpredictable times.

like image 91
Hans Passant Avatar answered Sep 20 '22 09:09

Hans Passant


Why the tasks are not running immediately after StartNew?

Regarding MSDN StartNew() will schedule a task for execution.

Calling StartNew is functionally equivalent to creating a Task using one of its constructors and then calling Start to schedule it for execution. However, unless creation and scheduling must be separated, StartNew is the recommended approach for both simplicity and performance.

Since TPL uses ThreadPool threads - sometimes it have to do some work in order to reserve and start a ThreadPool thread for a particular task execution. If you need explicitly run a separate thread without any intermediate mechanism like TPL's TaskScheduler - create and start a thread manually and obviuosly you will not have such neat stuff like continuation.

like image 28
sll Avatar answered Sep 19 '22 09:09

sll