使用Task.Run运行异步操作委托与默认操作委托有何区别?

17

我正在努力理解async/await,认为自己已经了解了一些用法。但是仍然不太清楚在下面这种情况下实际的好处是什么。

看一下Task.Run的用法。第一个方法使用普通委托并使用Thread.Sleep,但第二个方法使用'async'委托和Task.Delay。

我的问题是:这与该方法有何区别(或者说没有区别)?

该方法本身是异步方法。此代码通过Task.Run创建了一个单独的线程,并且该线程除了执行该委托外没有其他事情可做。因此,即使它在Task.Delay上使用了await,但在这种情况下有什么用处,因为该线程无论如何都是个孤立的线程,即使仅使用Thread.Sleep,该线程也会进行内容切换以让处理器切换到其他线程。

// The task thread uses a async delegate
public async Task<bool> RetrySendEmail(MailMessage message)
{
      bool emailSent = false;
      await (Task.Run(***async ()*** =>
      {
            for (int i = 0; i < 3; i++)
            {
                 if (emailSent)
                      break;
                 else
                      // Wait for 5 secs before trying again
                      ***await Task.Delay(5000);***

                 try
                 {
                      Smtphost.Send(message);
                      emailSent = true;
                      break;
                 }
                 catch (Exception e) { emailSent = false; // log; }
            }
            return emailSent;
      }));
}

// The task thread uses a normal delegate 
public async Task<bool> RetrySendEmail(MailMessage message)
{
      bool emailSent = false;
      await (Task.Run(***()*** =>
      {
            for (int i = 0; i < 3; i++)
            {
                 if (emailSent)
                      break;
                 else
                      // Wait for 5 secs before trying again
                      ***Thread.Sleep(5000);***

                 try
                 {
                      Smtphost.Send(message);
                      emailSent = true;
                      break;
                 }
                 catch (Exception e){ emailSent = false; // log; }
            }
                 return emailSent;
        }));
}
2个回答

13
我的问题是:这样做有什么区别吗(或者没有区别)?
有几个区别:
1. 在 `Task.Run` 中使用异步委托意味着你实际上运行了一个 `Task`。但这一点被隐藏了,因为 `Task.Run` 是异步感知的,并为你取消包装内部任务。Task.Factory.StartNew 并没有这样做。
2.当你在 `Task.Run` 中使用异步委托时,你创建了一个新的线程,然后在命中 `await Task.Delay` 时暂停控制权。延续将在任意线程池线程上运行。此外,编译器会将委托转换为状态机。
如果你使用普通委托,则创建一个线程,同步地阻塞它5秒钟,然后从离开的那个点继续执行。没有状态机,也没有 yield。
所以,即使它在 Task.Delay 上使用了 yield,那么在这种情况下有什么用处呢?由于该线程仅用于单独工作,并且即使它只使用 Thread.Sleep,线程仍然会切换上下文以让处理器屈服于其他线程。
使用 asyncTask.Run 的用途可能是当你想要同时执行 CPU 和 IO 绑定的工作,全部在专用线程中完成时。你的想法是正确的,在异步委托 yield 后,它返回到任意线程。然而,如果你没有使用 `Task.Run`,并且异步方法在附加了自定义同步上下文(如 WinformsSynchronizationContext)的线程上执行,则 `await` 之后的任何工作都会返回到 UI 消息循环,除非你使用了 `ConfigureAwait(false)`。说实话,我还没有看到很多正确使用 Task.Runasync 的场景。但有时确实有意义。

1
Winforms场景:Task.Run(() => ...I/O...) vs Task.Run(async() => ...I/O...)。在这里,我在循环中执行了25个爬取请求,但是我没有感觉到任何性能(时间)上的差异。 - Legends
顺便提一下,我使用 Task.run 是因为 ScrapingLib 还不支持 async/await - Legends

8
区别在于你浪费了一个线程和它所分配的时间片。当你阻塞一个线程5秒钟时,该线程无法用于系统的其他部分来进行实际的CPU工作。这也会创建一个上下文切换,因为该线程无法执行任何其他操作。当你使用Task.Delay而不是Thread.Sleep释放该线程时,该线程可以返回到线程池中,获取等待任务并执行它。总体而言,当你能够释放线程时,使你的应用程序更具可扩展性和效率,因为它需要更少的资源来完成相同的工作或完成更多的工作所需的相同数量的资源。
当你的操作真正异步时,没有必要使用Task.Run(除非你需要一个后台线程)。你只需调用该方法并等待返回的任务即可。
public async Task<bool> RetrySendEmail(MailMessage message)
{
    bool emailSent = false;
    for (int i = 0; i < 3; i++)
    {
         if (emailSent)
              break;
         else
              await Task.Delay(5000);

         try
         {
              Smtphost.Send(message);
              emailSent = true;
              break;
         }
         catch (Exception e) { emailSent = false; // log; }
    }
    return emailSent;
}

谢谢,这很有帮助。虽然有点偏离异步的主题,但在这个例子中,我创建了一个新的线程(使用Task.Run,因为'Smtphost.Send'是一个阻塞IO调用。所以,我认为将其转移到单独的线程上是有意义的。但你认为这样做不会有任何区别吗? - Everything Matters
1
@EverythingMatters Task.Run 不会创建线程,它使用了一个 ThreadPool 线程。你可以用它来将工作分配到不同的线程上(比如说将 GUI 线程解锁),但这并不能提高性能。如果原始线程已经是一个 ThreadPool 线程,阻塞另一个线程就没有任何价值了。 - i3arnon

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