31

Windows 7, Intel CORE i3, 64 bit, RAM 4Gb, 2.27 GHz
.NET Framework 4.0

I have the following code:

static void Main(string[] args)
{
    var timer = new Stopwatch();
    timer.Start();

    for (int i = 0; i < 0xFFF; ++i)
    {
        // I use one of the following line at time
        Task.Factory.StartNew(() => { });
        new Thread(() => { }).Start();
    }

    timer.Stop();

    Console.WriteLine(timer.Elapsed.TotalSeconds);
    Console.ReadLine();
}

If I use Task the output is always less then 0.01 seconds, but if I use Thread the output is always greater than 40 seconds!
How is it possible? Why so much difference?

Nick
  • 10,309
  • 21
  • 97
  • 201
  • 17
    One starts 4096 threads, the other queues 4096 tasks in a queue.. you're not measuring anything other than that. Pointless.. – Kieren Johnstone Oct 29 '12 at 15:57
  • 2
    Knowing that it is 4000x times more expensive to start threads than it is to add things to a queue is certainly good to know. – Bryan Rayner Apr 15 '21 at 22:49

6 Answers6

43

The two are not the same.

When you use Task.Factory.StartNew, you're scheduling a task to run on the ThreadPool. When you make a new Thread, you're having to create and start a new thread.

In the first case, the threads are already created and reused. This causes the overhead of scheduling the tasks to be far lower, as the threads don't have to be created each iteration.

Note that the behavior is not the same, however. When creating a separate thread, each task is getting it's own thread. They will all get started right away. When using Task.Factory.StartNew, they're put into the scheduler to run on the ThreadPool, which will (potentially) limit the number of concurrent threads started. This is usually a good thing, as it prevents overthreading from occurring.

Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
  • So, if I use Task is possible that it runs the specified delegate one at a time; ie only when the previous is completed the Task class starts the next? – Nick Oct 29 '12 at 16:01
  • [Task.ContinueWith](http://msdn.microsoft.com/en-us/library/dd235663.aspx): `var t = Task.Factory.StartNew(...).ContinueWith(...)`. – user7116 Oct 29 '12 at 16:02
  • @Nick You can use a continuation, or, if you want to block, call `.Wait()` or `.Result` on the `Task`/`Task`. That being said, if you want to process items *in order*, you might want to look at `BlockingCollection` and create a producer/consumer scenario instead. – Reed Copsey Oct 29 '12 at 16:08
  • @sixlettervariables but is this the default behavior for Task? ie: if i run a loop (as I wrote in my question) is it the same of `Task.Factory.StartNew(...).ContinueWith(...)`? – Nick Oct 29 '12 at 16:09
  • @Nick No. `Task.Factory.StartNew` will schedule all of the tasks to run concurrently. You need extra work if you want to make them run "in order". – Reed Copsey Oct 29 '12 at 16:15
  • @ReedCopsey How is it possible? I don't understand... If I use Thread I create a new thread for each call and each call runs concurrently, this is easy to understand. But if I use Task I don't create I new thread so how can each call runs concurrently if the method does not run in a separate thread? – Nick Oct 29 '12 at 16:18
  • @Nick You're using the `ThreadPool` (by default) when you create a Task. The ThreadPool already creates a set of threads, and puts the work on the threads for you. The difference is that the threads already exist, so you don't have the overhead of creating it yourself. – Reed Copsey Oct 29 '12 at 16:20
  • @ReedCopsey ok, it's clear now. A last thing.. why these threads already exist? When they were created? – Nick Oct 29 '12 at 16:22
  • @Nick: tasks are not threads. Tasks are a unit of work you would like run. It just so happens that the default task scheduler assigns them to a pool of threads which the framework manages for you. In the incredibly rare case where you *absolutely must have every unit of work in parallel at the same time* then you will have to use threads. But even then, once you oversubscribe your processor...you will not have every unit of work running in parallel physically. – user7116 Oct 29 '12 at 16:22
  • @Nick See "The Managed Thread Pool": http://msdn.microsoft.com/en-us/library/0ka9477y.aspx – Reed Copsey Oct 29 '12 at 16:27
4

Every time you start a Task it goes into a pool to be served by a number of threads, many of which may be pre-created. There is an M:N ratio of tasks to threads in the pool.

Every time you start a Thread it creates a new thread and all of the overhead associated with thread creation. Since you are explicitly creating a thread, there is a 1:1 ratio of threads.

The closer the ratio of tasks to threads reaches 1, the "slower" task startup it will take. In reality, the ThreadPool ensures the ratio stays much higher than 1.

user7116
  • 63,008
  • 17
  • 141
  • 172
3

You have an issue with your test, in that you don't wait for each Thread/Task to finish.

Task uses a queue, so its much faster to create a Task than a Thread.

I'll bet that even if you waited for Tasks/Threads to finish, that using a Task is faster. The overhead of creating and then destroying a Thread is high. That's why the Task.Factory was created!

Richard Schneider
  • 34,944
  • 9
  • 57
  • 73
1

Creating new threads is slow, but not that slow. Nick reported ~10ms/thread. Most likely it happened under Visual Studio debugger. I am getting ~3.9ms per new thread under Visual Studio debugger. I am getting ~0.15ms per new thread without debugger.

http://dennisgorelik.livejournal.com/125269.html?thread=2238805#t2238805

Dennis Gorelik
  • 1,234
  • 1
  • 11
  • 15
0

Task.Factory.StartNew() does not start a task immediately it just schedules it so a TaskScheduled would be able starting it a bit later (depends on number of available threads/tasks).

MSDN saying that after Thread.Start() operating system can schedule it for execution, interactions with OS is much slower than with .NET Framework's TaskScheduler but not in such degree.

And back to your example, 0xFFF == 4095, so you are scheduling 4095 threads and this takes 40 seconds. 102 threads in a second is a pretty good timing! :)

sll
  • 61,540
  • 22
  • 104
  • 156
0

Calling Task.Factory.StartNew doesn't necessarily create a new thread, they are managed by the TaskScheduler based upon how many cores etc the machine has that is running the code.

If you schedule (by calling Task.Factory.StartNew) more tasks than can be concurrently run, they will be queued and run as more resources become available.

Trevor Pilley
  • 16,156
  • 5
  • 44
  • 60