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

Tam*_*aky 10 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

至于所知道的,TestMethod1TestMethod2应该做的完全一样的.一个使用TPL,两个使用普通的香草线程.一个需要1:30分钟,两个需要0.54秒.

为什么?

Luk*_*oid 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);
    }
    
    Run Code Online (Sandbox Code Playgroud)
  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());            
    }
    
    Run Code Online (Sandbox Code Playgroud)

你性能低下的原因是因为ThreadPool如果需要的话,"慢慢"会产生新的线程,如果你阻塞它可用的几个线程,你会遇到明显的减速.这就是为什么ThreadPool它仅用于运行短任务.

如果您打算使用长时间阻塞操作,Task那么请确保TaskCreationOptions.LongRunning在创建Task实例时使用(这将创建一个新的底层Thread而不是使用ThreadPool).

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

ThreadPool.SetMinThreads(500, 500);
Run Code Online (Sandbox Code Playgroud)

这表明新ThreadPool线程的"缓慢"产生导致了您的瓶颈.