Task.Factory.StartNew vs Task.Factory.FromAsync

本文关键字:Task Factory FromAsync StartNew vs | 更新日期: 2023-09-27 18:20:48

让我们假设我们有一个I/O绑定方法(比如一个进行DB调用的方法)。此方法既可以在中同步运行,也可以在中异步运行。也就是说,

  1. 同步:

    IOMethod()
    
  2. 异步:

    BeginIOMethod()
    EndIOMethod()
    

然后,当我们以如下所示的不同方式执行该方法时,在资源利用率方面的性能差异是什么?

  1. var task = Task.Factory.StartNew(() => { IOMethod(); });
    task.Wait();
    
  2. var task = Task.Factory.FromAsync(BeginIOMethod, EndIOMethod, ... );
    task.Wait();
    

Task.Factory.StartNew vs Task.Factory.FromAsync

var task = Task.Factory.StartNew(() => { IOMethod(); });
task.Wait();

这将在执行IOMethod()时阻塞线程池线程,也会因为Wait()而阻塞当前线程。阻塞的线程总数:2。


var task = Task.Factory.FromAsync(BeginIOMethod, EndIOMethod, ... );
task.Wait();

这将(很可能)在不使用线程的情况下异步执行操作,但由于Wait(),它将阻塞当前线程。阻塞的线程总数:1。


IOMethod();

这将在执行IOMethod()时阻塞当前线程。阻塞的线程总数:1。

如果你需要阻塞当前线程,或者阻塞它对你来说是可以的,那么你应该使用它,因为尝试使用TPL实际上不会给你任何东西。


var task = Task.Factory.FromAsync(BeginIOMethod, EndIOMethod, ... );
await task;

这将在不使用线程的情况下异步执行操作,并且由于await,它还将等待操作异步完成。阻止的线程总数:0。

如果你想利用异步,并且你可以使用C#5.0,这就是你应该使用的。


var task = Task.Factory.FromAsync(BeginIOMethod, EndIOMethod, ... );
task.ContinueWith(() => /* rest of the method here */);

这将在不使用线程的情况下异步执行操作,并且由于ContinueWith(),它还将等待操作异步完成。阻止的线程总数:0。

如果你想利用异步,而不能使用C#5.0,那么这就是你应该使用的。

(1)将(很可能)导致.NET线程池处理您的Task

(2) 将使用BeginIOMethod/EndIOMethod对本机使用的任何机制来处理异步部分,异步部分可能涉及也可能不涉及.NET线程池。

例如,如果您的BeginIOMethod正在通过internet发送TCP消息,并且稍后收件人将向您发送TCP消息作为响应(由EndIOMethod接收),则.NET线程池不会提供该操作的异步性质。正在使用的TCP库提供异步部分。

这可以通过使用TaskCompletionSource类来实现。Task.Factory.FromAsync可以创建一个TaskCompletionSource<T>,返回其Task<T>,然后使用EndIOMethod作为触发器,将Result放入调用时从Task.Factory.FromAsync返回的Task<T>中。

资源利用率方面的性能差异是什么?

(1)和(2)之间的区别主要在于.NET线程池是否要添加其工作负载。通常,如果只有Begin.../End...对,则正确的做法是选择Task.Factory.FromAsync,否则选择Task.Factory.StartNew


如果您使用的是C#5.0,那么您应该使用非阻塞await task;而不是task.Wait();。(参见svick的答案。)