使用C#在线程和任务之间的性能比较

时间:2018-10-21 10:43:06

标签: c# multithreading task threadpool

我试图了解使用常见的ThreadTask之间的好处。前者进入System.Threading命名空间,后者进入System.Threading.Tasks命名空间。 因此,为了玩耍并熟悉它们,我在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));
    }
}

因此,根据我的理解,这些任务应该性能更高,因为它们在创建和启动新线程时会使用ThreadPool中可用的线程,这会非常消耗资源。 该程序记录两个事件。滴答声的数量CRL需要两次创建两种对象,并且在线程和提供的委托的实际执行之间(在我的情况下为ExecuteAsync中,滴答声是创建的。)

出乎我意料的事情发生了:

  

使用线程:11372 Hello World!使用线程之间的区别   初始化和执行是5482。使用TPL:333004 Hello World!   使用TPL,初始化和执行之间的差异为0

因此,与使用Tasks相比,使用经典Threads似乎更具性能。 但是对我来说,这里有些奇怪。有人能启发我这个话题吗? 谢谢。

1 个答案:

答案 0 :(得分:4)

第一次执行方法总是比较昂贵的:程序集被延迟加载,并且该方法可能尚未被JITted。

例如,如果我们采用您的基准测试(用秒表替换DateTime以获得精度)并再次调用Task.Factory.StartNew

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

我的计算机上的结果是:

  

使用线程:00:00:00.0002071

     

世界您好!使用Thread,初始化和执行之间的区别是00:00:00.0004732

     

使用TPL:00:00:00.0046301

     

世界您好!使用TPL,初始化和执行之间的区别是00:00:00.0048927

     

使用TPL:00:00:00.0000027

     

世界您好!使用TPL,初始化和执行之间的区别是00:00:00.0001215

我们可以看到第二个呼叫比第一个呼叫快三个数量级。

使用真实的基准框架(例如BenchmarkDotNet),我们可以获得更加可靠的结果:

  Method |       Mean |     Error |    StdDev |
-------- |-----------:|----------:|----------:|
 Threads | 137.426 us | 1.9170 us | 1.7932 us |
   Tasks |   2.384 us | 0.0322 us | 0.0301 us |

话虽如此,再说几句话:

  1. 您的比较不公平。您正在比较线程的创建与通过任务API在线程池中排队任务的比较。为了公平起见,您应该改用ThreadPool.UnsafeQueueWorkItem(这样可以在不使用任务API的情况下使用线程池)

  2. 是否使用线程或任务不应该是性能问题。确实更多是为了方便。除非您要处理低延迟或非常高的吞吐量,否则性能差异几乎不会对您的应用程序产生任何影响。