3

Do threads run faster than tasks?

The speed of my application improved only by replacing one line of code with thread that used task.

As a result, I decided to conduct a small experiment as follows:

using System.Diagnostics;

void LongRunning()
{
    Thread.Sleep(3000);
}
Stopwatch s1 = new Stopwatch();
TimeSpan ts;
string elapsedTime;

//thread----------------------
s1.Reset();
s1.Start();
var threadList = new List<Thread>();
for (int i = 0; i < 100; i++)
{
    Thread t = new Thread(LongRunning);
    threadList.Add(t);
    t.Start();
}

foreach (var th in threadList)
{
    th.Join();
}

s1.Stop();
ts = s1.Elapsed;
elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}",
    ts.Hours, ts.Minutes, ts.Seconds,
    ts.Milliseconds / 10);
Console.WriteLine("RunTime Thread " + elapsedTime);


//task----------------------
s1.Reset();
s1.Start();
var taskList = new List<Task>();
for (int i = 0; i < 100; i++)
    taskList.Add(Task.Factory.StartNew(LongRunning));


Task.WaitAll(taskList.ToArray());
s1.Stop();
ts = s1.Elapsed;
elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}",
   ts.Hours, ts.Minutes, ts.Seconds,
   ts.Milliseconds / 10);
Console.WriteLine("RunTime Task " + elapsedTime);
Console.ReadLine();

By using tasks, it takes ~25 seconds, and by using thread, it takes only ~4 seconds

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
bill
  • 35
  • 5
  • 1
    The answer is it depends. In your specific case it seems threads are faster. The only way to tell is to expiriment – mousetail Nov 30 '22 at 06:57
  • In tasks, you better use `await Task.Delay` instead of `Thread.Sleep` as the latter will block one of the thread pool threads. – Klaus Gütter Nov 30 '22 at 07:06
  • @KlausGütter if you combine `async`/`await` with the `Task.Factory.StartNew` method, all sorts of [weird things](https://stackoverflow.com/questions/74612611/c-running-an-async-lambda-using-task-run-new-task-task-factory-startne) are going to happen. – Theodor Zoulias Nov 30 '22 at 07:08
  • Replace the Thread method with `async Task LongRunning2() => await Task.Delay(3000);`, then `taskList.Add(LongRunning2());` and finally `await Task.WhenAll(taskList);` – Jimi Nov 30 '22 at 07:12
  • @mousetail experimenting is good, but understanding at a deeper level is better. See [Evk's](https://stackoverflow.com/a/74623907/11178549) answer as an example of understanding. If you don't know the theory, and you are just doing experiments again and again after doing changes randomly every time, the chances of ending up with the optimal solution are slim. – Theodor Zoulias Nov 30 '22 at 07:24
  • In general there is no way to tell. IO is faster in tasks while CPU is faster in threads but it's very hard to tell for a complex task what the proportion of IO to CPU is. Of course in this case there was just a bug in the program causing a performance issue which is entirely separate from the tasks vs threads issue. – mousetail Nov 30 '22 at 07:27

1 Answers1

4

This line:

Task.Factory.StartNew(LongRunning);

Schedules LongRunning to be run on a thread pool thread. Thread pool is a set of reusable threads, and it has logic behind managing this set of threads. When you attempt to schedule some work to the pool and it does not have a free thread available - it does not necessary spawn a new thread right away. It might wait a bit to see if some of currently busy threads will become available soon.

For this reason it's not a good idea to block thread pool threads for a long time, but this is exactly what you are doing in this example - you block thread pool threads for 3 seconds with Thread.Sleep(3000). First few iterations of your loop take all available thread pool threads, and then on each next iteration thread pool waits a bit to see if one of the existing threads will become available. It does not so then it adds a new thread into the pool and runs your work on it.

This waiting by thread pool is why your "task" code takes more time to complete.

If you intend to perform long blocking operation, you can use:

Task.Factory.StartNew(LongRunning, TaskCreationOptions.LongRunning);
Evk
  • 98,527
  • 8
  • 141
  • 191
  • I would also add the `TaskScheduler.Default` as argument, so that the code conforms with the [CA2008](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2008) guideline. Otherwise an unknown ambient scheduler could interpret the `TaskCreationOptions.LongRunning` option in any arbitrary way. – Theodor Zoulias Nov 30 '22 at 07:05