C# 为什么在线程中运行异步操作比纯任务或纯线程操作慢得多

C# 为什么在线程中运行异步操作比纯任务或纯线程操作慢得多,c#,multithreading,performance,asynchronous,async-await,C#,Multithreading,Performance,Asynchronous,Async Await,在调查一些性能问题的过程中,我们偶然发现了一些我们不知道原因的结果 我们尝试以不同的循环计数和延迟运行异步操作的循环,我们有3种不同的构造,其中案例2在线程数量增加时运行得慢得多。我们永远不会实际使用案例2中那样的代码,但是下面概述的结果的解释是什么: 案例1: for (int i = 0; i < count; i++) { tasks.Add(Task.Delay(delay)); } Task.WaitAll(tasks.ToArray()); 完整的测试程序如下: us

在调查一些性能问题的过程中,我们偶然发现了一些我们不知道原因的结果

我们尝试以不同的循环计数和延迟运行异步操作的循环,我们有3种不同的构造,其中案例2在线程数量增加时运行得慢得多。我们永远不会实际使用案例2中那样的代码,但是下面概述的结果的解释是什么:

案例1:

for (int i = 0; i < count; i++)
{
    tasks.Add(Task.Delay(delay));
}
Task.WaitAll(tasks.ToArray());
完整的测试程序如下:

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            RunAsync(1, 50);
            RunAsync(5, 50);
            RunAsync(10, 50);
            RunAsync(50, 50);
            RunAsync(100, 50);
            Console.WriteLine("==================================================");
            RunAsyncInThread(1, 50);
            RunAsyncInThread(5,50);
            RunAsyncInThread(10,50);
            RunAsyncInThread(50,50);
            RunAsyncInThread(100,50);
            Console.WriteLine("==================================================");
            RunThread(1, 50);
            RunThread(5,50);
            RunThread(10,50);
            RunThread(50,50);
            RunThread(100,50);

            //RunAsyncInThread(20, 50);
            //RunAsyncInThread(40, 50);
            //RunAsyncInThread(80, 50);
            //RunAsyncInThread(160, 50);
            //RunAsyncInThread(320, 50);
            Console.WriteLine("Press enter:");
            Console.ReadLine();
        }

        private static void RunAsyncInThread(int count, int delay)
        {
            Console.WriteLine("RunAsyncInThread, count = {0}, delay = {1} ", count, delay);
            var now = DateTime.UtcNow;
            var tasks = new List<Task>();
            for (int i = 0; i < count; i++)
            {
                var task = Task.Run(() => { Task.Delay(delay).Wait(); });
                tasks.Add(task);
            }
            Task.WaitAll(tasks.ToArray());
            var elapsed = DateTime.UtcNow - now;
            Console.WriteLine("Task.Run {0} times with Task.Delay of {1} ms. took {2}, average = {3}. ", count, delay, elapsed, TimeSpan.FromTicks(elapsed.Ticks / count));
        }

        private static void RunAsync(int count, int delay)
        {
            Console.WriteLine("RunAsync, count = {0}, delay = {1} ",count,delay);
            var now = DateTime.UtcNow;
            var tasks = new List<Task>();
            for (int i = 0; i < count; i++)
            {
                tasks.Add(Task.Delay(delay));
            }
            Task.WaitAll(tasks.ToArray());
            var elapsed = DateTime.UtcNow - now;
            Console.WriteLine("Async execution {0} times with Task.Delay of {1} ms. took {2}, average = {3}. ", count, delay, elapsed, TimeSpan.FromTicks(elapsed.Ticks / count));
        }

        private static void RunThread(int count, int delay)
        {
            Console.WriteLine("RunThread, count = {0}, delay = {1} ",count,delay);
            var now = DateTime.UtcNow;
            var tasks = new List<Task>();
            for (int i = 0; i < count; i++)
            {
                tasks.Add(Task.Run(() => Thread.Sleep(delay)));
            }
            Task.WaitAll(tasks.ToArray());
            var elapsed = DateTime.UtcNow - now;
            Console.WriteLine("Thread execution {0} times with Task.Delay of {1} ms. took {2}, average = {3}. ", count, delay, elapsed, TimeSpan.FromTicks(elapsed.Ticks / count));
        }

    }
}

为什么调度线程池线程启动异步操作,然后坐在那里等待异步操作完成,而不是直接执行异步操作会更慢

您节省了在线程池中等待调度操作的开销,并且如果线程池已饱和,比如说因为您突然向它发送了100个请求,这可能需要一些时间。一旦调度了操作,它就可以最终开始执行异步操作

为什么创建一个全新的线程以启动一个异步操作,然后等待它完成比仅仅启动一个异步操作要慢


因为您需要花费所有的时间和精力来创建一个全新的线程,并等待它第一次被调度,然后才能启动异步操作。

为什么调度线程池线程来启动异步操作会比较慢,然后坐在那里等待异步操作完成,而不是直接执行异步操作

您节省了在线程池中等待调度操作的开销,并且如果线程池已饱和,比如说因为您突然向它发送了100个请求,这可能需要一些时间。一旦调度了操作,它就可以最终开始执行异步操作

为什么创建一个全新的线程以启动一个异步操作,然后等待它完成比仅仅启动一个异步操作要慢


因为您需要花费所有的时间和精力来创建一个全新的线程,并等待它第一次被安排好,然后才能开始异步操作。

不要使用DateTime。现在来看看某个线程需要多长时间。它没有足够的精度或准确性来有意义地做到这一点。使用秒表。与你的陈述相比,情况2似乎是目前为止最快的。第一个和第三个是缓慢的…你正在用等待把线程池弄得一团糟。ThreadPool.SetMintThreadS100,4是一个大锤式解决方案,但它是一个应该解决的结构性问题。很难从人工测试代码中进行逆向工程。Servy是非常正确的,您混淆了这些情况。使用Task.Run=>{Task.Delaydelay.Wait;}最慢。它似乎是按顺序运行的。我不太清楚为什么睡眠和等待的行为不同,但等待会在一个内部事件上阻塞。我相信这实际上是内部的旋转等待,这可能是问题的一部分。是的,我把案例1和案例2混淆了。很抱歉造成混淆。不要使用DateTime。现在来看看某件事情需要多长时间。它没有足够的精度或准确性来有意义地做到这一点。使用秒表。与你的陈述相比,情况2似乎是目前为止最快的。第一个和第三个是缓慢的…你正在用等待把线程池弄得一团糟。ThreadPool.SetMintThreadS100,4是一个大锤式解决方案,但它是一个应该解决的结构性问题。很难从人工测试代码中进行逆向工程。Servy是非常正确的,您混淆了这些情况。使用Task.Run=>{Task.Delaydelay.Wait;}最慢。它似乎是按顺序运行的。我不太清楚为什么睡眠和等待的行为不同,但等待会在一个内部事件上阻塞。我相信这实际上是内部的旋转等待,这可能是问题的一部分。是的,我把案例1和案例2混淆了。很抱歉造成混淆。是的,我同意,但案例2和案例3之间的差异仍然很大,即都启动了大量线程。@StigSchmidtNielsson是的。他们会在不同的情况下表现不同;这是线程池化的直接结果。这意味着创建新线程所浪费的时间更少,因为可以重复使用的线程数量更少,但这也意味着,当池中的工作超过其处理能力时,它的性能会更差。您所做的事情正是线程池最无法处理的事情;您使用的是最不可接受的添加工作模式。是的,我同意,但案例2和案例3之间的差异仍然很大,即两种情况都启动了大量线程。@StigSchmidtNielsson是的。他们会守规矩的 在不同的情况下会有所不同;这是线程池化的直接结果。这意味着创建新线程所浪费的时间更少,因为可以重复使用的线程数量更少,但这也意味着,当池中的工作超过其处理能力时,它的性能会更差。您所做的事情正是线程池最无法处理的事情;您使用的是最不可接受的添加工作模式。
for (int i = 0; i < count; i++)
{
    tasks.Add(Task.Run(() => Thread.Sleep(delay)));
}
Task.WaitAll(tasks.ToArray());
RunAsync, count = 1, delay = 50
Async execution 1 times with Task.Delay of 50 ms. took 00:00:00.0510000, average = 00:00:00.0510000.
RunAsync, count = 5, delay = 50
Async execution 5 times with Task.Delay of 50 ms. took 00:00:00.0620000, average = 00:00:00.0124000.
RunAsync, count = 10, delay = 50
Async execution 10 times with Task.Delay of 50 ms. took 00:00:00.0660000, average = 00:00:00.0066000.
RunAsync, count = 50, delay = 50
Async execution 50 times with Task.Delay of 50 ms. took 00:00:00.0590000, average = 00:00:00.0011800.
RunAsync, count = 100, delay = 50
Async execution 100 times with Task.Delay of 50 ms. took 00:00:00.0620000, average = 00:00:00.0006200.
==================================================
RunAsyncInThread, count = 1, delay = 50
Task.Run 1 times with Task.Delay of 50 ms. took 00:00:00.0630000, average = 00:00:00.0630000.
RunAsyncInThread, count = 5, delay = 50
Task.Run 5 times with Task.Delay of 50 ms. took 00:00:00.0620000, average = 00:00:00.0124000.
RunAsyncInThread, count = 10, delay = 50
Task.Run 10 times with Task.Delay of 50 ms. took 00:00:00.7200000, average = 00:00:00.0720000.
RunAsyncInThread, count = 50, delay = 50

WHY ARE THESE SO SLOW:

Task.Run 50 times with Task.Delay of 50 ms. took 00:00:15.8100000, average = 00:00:00.3162000.
RunAsyncInThread, count = 100, delay = 50
Task.Run 100 times with Task.Delay of 50 ms. took 00:00:34.0600000, average = 00:00:00.3406000.
==================================================
RunThread, count = 1, delay = 50
Thread execution 1 times with Task.Delay of 50 ms. took 00:00:00.0500000, average = 00:00:00.0500000.
RunThread, count = 5, delay = 50
Thread execution 5 times with Task.Delay of 50 ms. took 00:00:00.0500000, average = 00:00:00.0100000.
RunThread, count = 10, delay = 50
Thread execution 10 times with Task.Delay of 50 ms. took 00:00:00.0500000, average = 00:00:00.0050000.
RunThread, count = 50, delay = 50
Thread execution 50 times with Task.Delay of 50 ms. took 00:00:00.0500000, average = 00:00:00.0010000.
RunThread, count = 100, delay = 50
Thread execution 100 times with Task.Delay of 50 ms. took 00:00:00.1000000, average = 00:00:00.0010000.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            RunAsync(1, 50);
            RunAsync(5, 50);
            RunAsync(10, 50);
            RunAsync(50, 50);
            RunAsync(100, 50);
            Console.WriteLine("==================================================");
            RunAsyncInThread(1, 50);
            RunAsyncInThread(5,50);
            RunAsyncInThread(10,50);
            RunAsyncInThread(50,50);
            RunAsyncInThread(100,50);
            Console.WriteLine("==================================================");
            RunThread(1, 50);
            RunThread(5,50);
            RunThread(10,50);
            RunThread(50,50);
            RunThread(100,50);

            //RunAsyncInThread(20, 50);
            //RunAsyncInThread(40, 50);
            //RunAsyncInThread(80, 50);
            //RunAsyncInThread(160, 50);
            //RunAsyncInThread(320, 50);
            Console.WriteLine("Press enter:");
            Console.ReadLine();
        }

        private static void RunAsyncInThread(int count, int delay)
        {
            Console.WriteLine("RunAsyncInThread, count = {0}, delay = {1} ", count, delay);
            var now = DateTime.UtcNow;
            var tasks = new List<Task>();
            for (int i = 0; i < count; i++)
            {
                var task = Task.Run(() => { Task.Delay(delay).Wait(); });
                tasks.Add(task);
            }
            Task.WaitAll(tasks.ToArray());
            var elapsed = DateTime.UtcNow - now;
            Console.WriteLine("Task.Run {0} times with Task.Delay of {1} ms. took {2}, average = {3}. ", count, delay, elapsed, TimeSpan.FromTicks(elapsed.Ticks / count));
        }

        private static void RunAsync(int count, int delay)
        {
            Console.WriteLine("RunAsync, count = {0}, delay = {1} ",count,delay);
            var now = DateTime.UtcNow;
            var tasks = new List<Task>();
            for (int i = 0; i < count; i++)
            {
                tasks.Add(Task.Delay(delay));
            }
            Task.WaitAll(tasks.ToArray());
            var elapsed = DateTime.UtcNow - now;
            Console.WriteLine("Async execution {0} times with Task.Delay of {1} ms. took {2}, average = {3}. ", count, delay, elapsed, TimeSpan.FromTicks(elapsed.Ticks / count));
        }

        private static void RunThread(int count, int delay)
        {
            Console.WriteLine("RunThread, count = {0}, delay = {1} ",count,delay);
            var now = DateTime.UtcNow;
            var tasks = new List<Task>();
            for (int i = 0; i < count; i++)
            {
                tasks.Add(Task.Run(() => Thread.Sleep(delay)));
            }
            Task.WaitAll(tasks.ToArray());
            var elapsed = DateTime.UtcNow - now;
            Console.WriteLine("Thread execution {0} times with Task.Delay of {1} ms. took {2}, average = {3}. ", count, delay, elapsed, TimeSpan.FromTicks(elapsed.Ticks / count));
        }

    }
}