【问题标题】:Task.Factory.StartNew vs Task.Factory.FromAsyncTask.Factory.StartNew 与 Task.Factory.FromAsync
【发布时间】:2013-07-02 17:45:24
【问题描述】:

假设我们有一个 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();
    

【问题讨论】:

  • 简短的回答,当你使用FromAsync 时,你(可能)没有线程池线程坐在那里什么都不做,如果你使用StartNew,你就是。如果您正在做很多事情,那么强调线程池可能是一个性能问题。

标签: c# .net asynchronous task-parallel-library


【解决方案1】:
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,你应该使用这个。

【讨论】:

  • 我认为引入“task.Wait()”只是为了举例。我确定当任务执行时,OP 打算在此过程中做一些额外的事情。所以我认为你不应该把调用线程算作阻塞线程。但如果他是认真地等待,那么你是对的。
  • @Tombala 我在问题中看不到任何表明这一点的内容。但这肯定是有可能的。
【解决方案2】:

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

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

例如,如果您的 BeginIOMethod 正在通过 Internet 发送 TCP 消息,而稍后收件人将向您发送 TCP 消息作为响应(由 EndIOMethod 接收),那么.NET 线程池未提供该操作。正在使用的 TCP 库提供异步部分。

这可以通过使用TaskCompletionSource class 来完成。 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 的回答。)

【讨论】:

  • 不是(2)还需要启动一个线程来执行 BeginIOMethod 吗?那不还是线程池中的一个线程吗?根据this question 的说法,FromAsync 也使用了线程池。
  • @Tombala BeginIOMethod 在调用FromAsync 的线程上完全同步运行。 ---关于该问题的答案,请参阅此评论:“默认情况下,生成的任务将在线程池线程上执行” - 仅当操作实际上需要线程运行时。例如,I/O 操作将使用 I/O 完成端口而不是线程。这可以更好地扩展,因为线程是一种有限且有些昂贵的资源。
  • 正确,但对话的其余部分也指出这些回调可能发生在线程池线程上。因此,根据 Begin 和 End 的实现方式,利用率可能相同、更少,甚至更多。例如。如果 Begin 和 End 启动线程来执行它们的异步操作而不是端口回调,那么它们也有可能添加到线程池中。
  • “正确的做法是选择Task.Factory.FromAsync [or] Task.Factory.StartNew” 不,这里正确的做法是根本不使用TPL,或者使用FromAsync()并且非阻塞等等。
  • @svick “这里的正确做法是不使用 TPL” - 问题被标记为 TPL,所以为了回答所提出的问题,我将使用 TPL。我错过了你的观点?也许 OP 没有使用 C# 5?
猜你喜欢
  • 1970-01-01
  • 2016-04-22
  • 2011-06-27
  • 2012-03-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-31
  • 2013-06-04
相关资源
最近更新 更多