Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does running a hundred async tasks take longer than running a hundred threads?

Why does running a hundred async tasks take longer than running a hundred threads?

I have the following test class:

public class AsyncTests
{

    public void TestMethod1()
    {
        var tasks = new List<Task>();

        for (var i = 0; i < 100; i++)
        {
            var task = new Task(Action);
            tasks.Add(task);
            task.Start();
        }

        Task.WaitAll(tasks.ToArray());            
    }


    public void TestMethod2()
    {
        var threads = new List<Thread>();

        for (var i = 0; i < 100; i++)
        {
            var thread = new Thread(Action);
            threads.Add(thread);
            thread.Start();
        }

        foreach (var thread in threads)
        {
            thread.Join();
        }
    }

    private void Action()
    {
        var task1 = LongRunningOperationAsync();
        var task2 = LongRunningOperationAsync();
        var task3 = LongRunningOperationAsync();
        var task4 = LongRunningOperationAsync();
        var task5 = LongRunningOperationAsync();

        Task[] tasks = {task1, task2, task3, task4, task5};
        Task.WaitAll(tasks);
    }

    public async Task<int> LongRunningOperationAsync()
    {
        var sw = Stopwatch.StartNew();

        await Task.Delay(500);

        Debug.WriteLine("Completed at {0}, took {1}ms", DateTime.Now, sw.Elapsed.TotalMilliseconds);

        return 1;
    }
}

As far as can tell, TestMethod1 and TestMethod2 should do exactly the same. One uses TPL, two uses plain vanilla threads. One takes 1:30 minutes, two takes 0.54 seconds.

Why?

like image 323
Tamas Pataky Avatar asked Sep 28 '15 14:09

Tamas Pataky


People also ask

Is Async faster than multithreading python?

Tasks + async / await are faster in this case than a pure multi threaded code. It's the simplicity which makes async / await so appealing.

What is difference between task and thread in C#?

Differences Between Task And ThreadThe Thread class is used for creating and manipulating a thread in Windows. A Task represents some asynchronous operation and is part of the Task Parallel Library, a set of APIs for running tasks asynchronously and in parallel. The task can return a result.

Should I use task or thread?

It is always advised to use tasks instead of thread as it is created on the thread pool which has already system created threads to improve the performance. The task can return a result. There is no direct mechanism to return the result from a thread.


2 Answers

The Action method is currently blocking with the use of Task.WaitAll(tasks). When using Task by default the ThreadPool will be used to execute, this means you are blocking the shared ThreadPool threads.

Try the following and you will see equivalent performance:

  1. Add a non-blocking implementation of Action, we will call it ActionAsync

    private Task ActionAsync()
    {
        var task1 = LongRunningOperationAsync();
        var task2 = LongRunningOperationAsync();
        var task3 = LongRunningOperationAsync();
        var task4 = LongRunningOperationAsync();
        var task5 = LongRunningOperationAsync();
    
        Task[] tasks = {task1, task2, task3, task4, task5};
        return Task.WhenAll(tasks);
    }
    
  2. Modify TestMethod1 to properly handle the new Task returning ActionAsync method

    public void TestMethod1()
    {
        var tasks = new List<Task>();
    
        for (var i = 0; i < 100; i++)
        {
            tasks.Add(Task.Run(new Func<Task>(ActionAsync)));
        }
    
        Task.WaitAll(tasks.ToArray());            
    }
    

The reason you were having slow performance is because the ThreadPool will "slowly" spawn new threads if required, if you are blocking the few threads it has available, you will encounter a noticeable slowdown. This is why the ThreadPool is only intended for running short tasks.

If you are intending to run a long blocking operation using Task then be sure to use TaskCreationOptions.LongRunning when creating your Task instance (this will create a new underlying Thread rather than using the ThreadPool).

Some further evidence of the ThreadPool being the issue, the following also alleviates your issue (do NOT use this):

ThreadPool.SetMinThreads(500, 500);

This demonstrates that the "slow" spawning of new ThreadPool threads was causing your bottleneck.

like image 125
Lukazoid Avatar answered Sep 27 '22 02:09

Lukazoid


Tasks are executed on threads from the threadpool. The threadpool as a limited number of threads which are reused. All task, or all requested actions, are queued and executed by those threads when they are idle.

Let's assume your threadpool has 10 threads, and you have 100 tasks waiting, then 10 tasks are executed while the other 90 tasks are simply waiting in the queue untill the first 10 tasks are finished.

In the second testmethod you create 100 threads who are dedicated to their tasks. So instead of 10 threads running simultaniously, 100 threads are doing the work.

like image 30
Martin Mulder Avatar answered Sep 24 '22 02:09

Martin Mulder