Task.Factory.StartNew和Task.Factory.FromAsync的区别

51

假设我们有一个I/O绑定的方法(例如,一个方法会进行数据库调用)。该方法可以同步或异步运行。也就是说,

  • 同步:

  • IOMethod()
    
  • 异步:

    BeginIOMethod()
    EndIOMethod()
    
    然后,当我们按照以下所示的不同方式执行该方法时,从资源利用方面来看,性能差异是什么?
  • var task = Task.Factory.StartNew(() => { IOMethod(); });
    task.Wait();
    
  • var task = Task.Factory.FromAsync(BeginIOMethod, EndIOMethod, ... );
    task.Wait();
    

  • 2
    简而言之,当使用FromAsync时,你(可能)不会有一个线程池线程闲置无事可做,但如果你使用StartNew,就会有。如果你要处理大量的任务,过度使用线程池可能会导致性能问题。 - Servy
    重复:https://dev59.com/32445IYBdhLWcg3wIG19 - anton.burger
    2个回答

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

    当执行IOMethod()时,这将会阻塞线程池线程,并且由于Wait()的存在也会阻塞当前线程。总共被阻塞的线程数为2。


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

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


    这将(很可能)异步执行操作,而不使用线程,但由于使用了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,则应使用此方法。


    4
    我认为 "task.Wait()" 只是举例而已。我相信当任务正在执行时,OP 想要在其执行过程中做一些额外的事情。因此,我不认为您应该将调用线程视为被阻塞的线程。但是,如果他真的只是想等待,那么你是正确的。 - Tombala
    2
    @Tombala 我在问题中没有看到任何迹象表明这一点。但这当然是可能的。 - svick

    2
    (1) 会(很可能)导致.NET线程池处理您的Task
    (2) 将使用您的BeginIOMethod / EndIOMethod对应的机制来处理异步部分,这可能涉及或不涉及.NET线程池。
    例如,如果您的BeginIOMethod正在通过互联网发送TCP消息,并且稍后收件人将向您发送TCP响应消息(由EndIOMethod接收),则操作的异步性不是由.NET线程池提供的。使用的TCP库提供了异步部分。
    这可以通过使用TaskCompletionSource来实现。Task.Factory.FromAsync可以创建一个TaskCompletionSource<T>,返回其Task<T>,然后使用EndIOMethod作为触发器,将Result放入在调用Task.Factory.FromAsync时返回的Task<T>中。

    在资源利用方面有何性能差异?

    (1)和(2)之间的区别主要在于是否将.NET线程池的工作负载添加到其中。通常,正确的做法是选择Task.Factory.FromAsync,如果您只有一个Begin... / End...对,则选择Task.Factory.StartNew
    如果您正在使用C# 5.0,则应该使用非阻塞的await task;而不是task.Wait();。(请参见svick的答案。)

    (2) 难道不仍然需要启动一个线程来执行BeginIOMethod吗?这不还是线程池中的一个线程吗?根据这个问题,FromAsync也使用线程池。 - Tombala
    正确,但对话的其余部分也指向这些回调可能发生在线程池线程上的可能性。因此,根据Begin和End的实现方式,利用率可能相同、更少或甚至更多。例如,如果Begin和End启动线程来执行它们的异步操作而不是端口回调,则它们也可能会添加到线程池中。 - Tombala
    “正确的做法是选择 Task.Factory.FromAsync [或者] Task.Factory.StartNew” 不,这里正确的做法不是使用 TPL,而是使用 FromAsync() 和非阻塞等待。 - svick
    @svick “这里正确的做法是不使用TPL” - 问题标记了TPL,所以为了按照问题的要求回答,我会使用TPL。我不明白你的观点?也许OP没有使用C#5? - Timothy Shields
    从我的回答中,我强调了一点:“(2)将使用BeginIOMethod / EndIOMethod对来处理异步部分的任何机制,这可能涉及或不涉及.NET线程池。”那么当你说“他们可能也会添加到线程池”时,为什么要表现得好像你在说些新东西呢? - Timothy Shields
    显示剩余3条评论

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接