【问题标题】:Use async await or task?使用异步等待或任务?
【发布时间】:2014-09-15 13:58:52
【问题描述】:

您已经构建了一个复杂的计算算法。这需要相当长的时间才能完成,并且您希望确保您的应用程序保持响应。你是做什么的?

  • A.使用异步/等待。
  • 乙。同步运行代码。
  • C.使用 Task.Run。
  • D.使用 BackgroundWorker。

答案是 C。但是,有人可以解释为什么 A) 不正确吗?因为问题并没有说复杂算法受 CPU 限制。如果它受 CPU 限制,那么我们将不得不使用任务(我也不太理解的推理,尽管我知道任务确实有助于导致当前线程暂停,直到它们完成)。另外,请说明如何决定何时使用 async/await,以及使用 Tasks。

【问题讨论】:

  • 错误的二分法;答案是 A 和 C。请注意,在 ASP.NET 上,答案是 B。

标签: c# asynchronous task


【解决方案1】:

我认为“计算”意味着在没有其他信息的情况下受 CPU 限制的算法。如果算法是 IO 绑定 async/await 不仅可以接受而且是正确答案。

【讨论】:

  • 确实如此。当然,您可以采用 Dispatcher 方法。
  • @DimitarDimitrov,如果您使用 Task.Run,传递给它的委托可能会在 UI 线程以外的线程上执行,因此您的 UI 将保持响应在任何情况下,您都不会通过Wait()Result 阻止任务的完成。但是,如何选择处理任务的完成并与调用Task.Run 的线程进行通信首先取决于您:您可以使用await,或者您可以使用带有适当TaskScheduler 的任务延续,或者您可以显式捕获原始 SynchronizationContext 并从您的委托中回发给它,
  • (续)或者您可以使用 Dispatcher.InvokeControl.Invoke,或者您可以使用自己的 AsyncEnumerator (blogs.msdn.com/b/pfxteam/archive/2010/11/21/10094564.aspx)。我的观点是,有大约一百万种方法可以做到这一点,async/await 恰好是其中之一,即使这是每个人首先想到的(这很容易理解)。
  • @KirillShlenskiy 当然,你是 100% 正确的,我同意(尽管你有点迂腐),但问题是垃圾的事实仍然存在(恕我直言)。
  • @DimitarDimitrov,同意。
【解决方案2】:

我相信这个问题假设您有一个同步编程的算法。问题提到这是一个“计算”,模糊地暗示它受 CPU 限制。它也非常模糊地暗示该算法是同步编写的。例如:

public int CalculateStuff() {
     ....
}

我会考虑为这个方法创建一个异步对应项:

public async Task<int> CalculateStuffAsync() {
      return await Task.Run(() => CalculateStuff());
}

然后在您的用户界面中:

LoadingIndicator.IsEnabled = true;
ResultTextBox.Text = await CalculateStuffAsync();
LoadingIndicator.IsEnabled = false;

因此,正确的答案可能是 A 和 C。无论如何,这个问题太模糊,无法得出任何可靠的结论。

【讨论】:

  • @DimitarDimitrov 同意,​​除 B 之外的所有内容在技术上都是有效的
  • @BasBrekelmans,为受 CPU 限制的工作提供公共 xxxAsync 包装器是糟糕的设计。如果需要,调用者不能将CalculateStuff 包装在Task.Run 中。还要考虑以下几点:CalculateStuffAsync().Wait()。如果没有ConfigureAwait(false),您只是引入了一个死锁错误,直到您编写您的async 方法才存在,几乎为零。
  • @DimitarDimitrov,不要相信我的话。在安装了非空SynchronizationContext(即 Windows 窗体/WPF)的应用程序中运行您的代码,看看会发生什么。这是因为您的await 需要转换到原始SynchronizationContext 才能返回异步操作的结果。如果您通过Task.Wait 阻止线程,则该转换将永远不会发生,因为线程将忙于等待任务(永远不会完成)。将死。取出async/await直接返回任务,就解决了。
  • (或显然使用ConfigureAwait(false)
  • @DimitarDimitrov,没问题;并且请接受我的道歉错误地处理我的回复。当我写这篇文章时,我把你和 Bas Brekelmans 弄混了。
【解决方案3】:

你是做什么的?

A.使用异步/等待。

B.同步运行代码。

C.使用 Task.Run。

D.使用 BackgroundWorker。

在 C# 5.0 中实现了async/await 功能,以使异步代码“像编写同步代码一样简单”。如果您查看 WinAPI,您会发现几乎所有异步端点都公开了一个完全异步的 api,即there is no thread。进一步看,您会注意到那些相同的端点正在执行I/O bound 操作。

假设您的算法是CPU bound,并且以允许跨多个处理器进行有效计算的方式编写(例如,您的算法没有任何需要同步的共享状态),您可以利用@987654324 @ 在 .NET 4.0 中引入。 TPL 提供了对 ThreadPool 的抽象,这反过来又试图在多处理器环境中均匀地卸载工作。

await 关键字可用于任何awaitable 对象,这意味着该对象实现了GetAwaiter 方法。当您使用可等待对象时,您可能希望使用await,并希望在等待对象完成其工作后恢复执行。 Task 实现了可等待模式,用于描述将在未来完成的工作单元。您可以使用Task.Runawait,当您想要卸载工作线程上的一个工作单元并且想要在该工作完成之前将控制权返回给调用方法

在多个处理器之间分派 CPU 密集型工作的另一种方法是使用 Parallel 类,它公开了 Parallel.ForParallel.ForEach

附带说明,BackgroundWorker 也可用于卸载后台线程上的工作,如果那是提问的人所追求的。自 .NET 4.0 发布以来,建议使用 TPL。

总之,推荐使用任务并行库将工作卸载到后台线程的方法。您可以将它们与Parallel 库结合使用,以最大限度地提高算法的并行性。完成此操作后,测试您的代码以确保使用多个线程的开销不会超过同步运行算法所需的时间。

编辑

正如 Stephan 在评论中提到的,您可以结合 Parallel.ForEachTask.Run 来卸载后台线程内的并行循环:

var task = Task.Run(() => Parallel.ForEach(.. //Loop here));

【讨论】:

  • 组合方法也很有用:await Task.Run(() =&gt; Parallel.ForEach(...)) 将在 UI 异步处理的后台线程上执行并行工作。
猜你喜欢
  • 1970-01-01
  • 2014-03-18
  • 2014-09-06
  • 2013-02-10
  • 2017-07-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-08
相关资源
最近更新 更多