为什么运行一百个异步任务比运行一百个线程需要更长的时间?

时间:2015-09-28 14:04:36

标签: c# multithreading async-await

为什么运行一百个异步任务比运行一百个线程需要更长的时间?

我有以下测试类:

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;
    }
}

据我所知,TestMethod1TestMethod2应该完全相同。一个使用TPL,两个使用普通的香草线程。一个需要1:30分钟,两个需要0.54秒。

为什么?

2 个答案:

答案 0 :(得分:12)

Action方法目前因使用Task.WaitAll(tasks)而受阻。默认使用Task时,ThreadPool将用于执行,这意味着您正在阻止共享的ThreadPool线程。

尝试以下操作,您将看到相同的效果:

  1. 添加Action的非阻止实施,我们将其称为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. 修改TestMethod1以正确处理新的Task返回ActionAsync方法

    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());            
    }
    
  3. 你性能低下的原因是因为ThreadPool会“慢慢”产生新线程(如果需要),如果你阻止了它可用的几个线程,你会遇到明显的减速。这就是ThreadPool仅用于运行简短任务的原因。

    如果您打算使用Task运行长时间阻止操作,请确保在创建TaskCreationOptions.LongRunning实例时使用Task(这将创建新的基础Thread而不是使用ThreadPool)。

    ThreadPool是问题的进一步证据,以下内容也可以缓解您的问题(请勿使用此问题):

    ThreadPool.SetMinThreads(500, 500);
    

    这表明新ThreadPool线程的“缓慢”产生导致了您的瓶颈。

答案 1 :(得分:1)

任务在线程池的线程上执行。线程池作为有限数量的线程被重用。所有任务或所有请求的操作在空闲时由这些线程排队并执行。

假设你的线程池有10个线程,你有100个任务在等待,然后执行10个任务,而其他90个任务只是在队列中等待,直到前10个任务完成。

在第二种测试方法中,您创建了100个专用于其任务的线程。因此,不是10个线程同时运行,而是100个线程正在进行工作。