我试图了解使用常见的Thread
和Task
之间的好处。前者进入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似乎更具性能。 但是对我来说,这里有些奇怪。有人能启发我这个话题吗? 谢谢。
答案 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 |
话虽如此,再说几句话:
您的比较不公平。您正在比较线程的创建与通过任务API在线程池中排队任务的比较。为了公平起见,您应该改用ThreadPool.UnsafeQueueWorkItem
(这样可以在不使用任务API的情况下使用线程池)
是否使用线程或任务不应该是性能问题。确实更多是为了方便。除非您要处理低延迟或非常高的吞吐量,否则性能差异几乎不会对您的应用程序产生任何影响。