I'm trying to understand the benefit between using common Threads and Tasks. The first is into System.Threading namespace and the latter is into System.Threading.Tasks namespace.
So, just for playing and get familiar with them, I wrote this program in C#:
class Program
{
static void Main(string[] args)
{
long ticksAtStart = DateTime.UtcNow.Ticks;
new Thread(() => { ExecuteAsyn("Thread", DateTime.UtcNow.Ticks); }).Start();
Console.WriteLine("Using Thread: " + (DateTime.UtcNow.Ticks - ticksAtStart));
ticksAtStart = DateTime.UtcNow.Ticks;
Task g = Task.Factory.StartNew(() => ExecuteAsyn("TPL", DateTime.UtcNow.Ticks));
Console.WriteLine("Using TPL: " + (DateTime.UtcNow.Ticks - ticksAtStart));
g.Wait();
Console.ReadKey();
}
private static void ExecuteAsyn(string source, long ticksAtExecutionTime)
{
Console.WriteLine("Hello World! Using " + source + " the difference between initialization and execution is " + (DateTime.UtcNow.Ticks - ticksAtExecutionTime));
}
}
So in base of my understandings, the Tasks should be more performant because they use Thread available into the ThreadPool while creating and starting new Thread can be very resource-consuming.
The program logs two events. The number of ticks CRL takes two create the two kinds of object and the ticks between the Thread is created and the actual execution of the provided delegate, in my case ExecuteAsync.
Something I did not expect occurs:
Using Thread: 11372 Hello World! Using Thread the difference between initialization and execution is 5482 Using TPL: 333004 Hello World! Using TPL the difference between initialization and execution is 0
So it seems that using classical Threads is much more performant than using Tasks. But to me there is something strange here. Can anyone enlight me about this topic? Thank you.
Executing a method the first time is always more costly: assemblies are lazily loaded, and the method may not be JITted yet.
For instance, if we take your benchmark (replacing DateTime by Stopwatch for precision) and call Task.Factory.StartNew once more:
static void Main(string[] args)
{
var sw = Stopwatch.StartNew();
new Thread(() => { ExecuteAsyn("Thread", sw); }).Start();
Console.WriteLine("Using Thread: " + sw.Elapsed);
sw = Stopwatch.StartNew();
Task g = Task.Factory.StartNew(() => ExecuteAsyn("TPL", sw));
Console.WriteLine("Using TPL: " + sw.Elapsed);
g.Wait();
sw = Stopwatch.StartNew();
g = Task.Factory.StartNew(() => ExecuteAsyn("TPL", sw));
Console.WriteLine("Using TPL: " + sw.Elapsed);
g.Wait();
Console.ReadKey();
}
private static void ExecuteAsyn(string source, Stopwatch sw)
{
Console.WriteLine("Hello World! Using " + source + " the difference between initialization and execution is " + (sw.Elapsed));
}
The result on my computer is:
Using Thread: 00:00:00.0002071
Hello World! Using Thread the difference between initialization and execution is 00:00:00.0004732
Using TPL: 00:00:00.0046301
Hello World! Using TPL the difference between initialization and execution is 00:00:00.0048927
Using TPL: 00:00:00.0000027
Hello World! Using TPL the difference between initialization and execution is 00:00:00.0001215
We can see that the second call is faster than the first one by three orders of magnitude.
Using a real benchmark framework (for instance, BenchmarkDotNet), we can get much more reliable results:
Method | Mean | Error | StdDev |
-------- |-----------:|----------:|----------:|
Threads | 137.426 us | 1.9170 us | 1.7932 us |
Tasks | 2.384 us | 0.0322 us | 0.0301 us |
That said, a few additional remarks:
Your comparison is not fair. You're comparing the creation of a thread versus enqueuing a task on the threadpool via the tasks APIs. For fairness, you should use ThreadPool.UnsafeQueueWorkItem instead (which allows you to use the threadpool without the task API)
Whether to use Thread or Task shouldn't be a matter of performance. It's really more a matter of convenience. It's highly unlikely that the performance gap would make any difference in your application, unless you're dealing with low-latency or very high throughput.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With