【问题标题】:When to use the "await" keyword何时使用“await”关键字
【发布时间】:2013-05-26 11:58:01
【问题描述】:

我正在编写一个网页,它调用了一些网络服务。调用看起来像这样:

var Data1 = await WebService1.Call();
var Data2 = await WebService2.Call();
var Data3 = await WebService3.Call();

在代码审查期间,有人说我应该将其更改为:

var Task1 = WebService1.Call();
var Task2 = WebService2.Call();
var Task3 = WebService3.Call();

var Data1 = await Task1;
var Data2 = await Task2;
var Data3 = await Task3;

为什么?有什么区别?

【问题讨论】:

  • 除非它是某种复制问题,否则我看不出有任何理由一个体面的编译器不会优化额外的分配。不过,我对 C# 的了解还不够肯定。也许这只是一个语义问题,而您的团队更愿意明确表示调用是一项任务,而任务的结果是数据?
  • @JAB 这不是真的。这两个代码 sn-ps 的语义存在戏剧性的差异。
  • @servy 你的语义是什么?
  • @Servy 我已经纠正了。由于我很少使用 C#,我的印象是 await 用于允许异步方法的非阻塞变量分配,而实际上恰恰相反。
  • @JAB 你能想出更好的办法吗? (并不是说它在这一点上真的可以改变。)很多人花了很多时间思考什么是合适的。

标签: c# web-services task-parallel-library .net-4.5 async-await


【解决方案1】:

Servy 的回答是正确的;稍微扩展一下。有什么区别:

Eat(await cook.MakeSaladAsync());
Eat(await cook.MakeSoupAsync());
Eat(await cook.MakeSandwichAsync());

Task<Salad> t1 = cook.MakeSaladAsync();
Task<Soup> t2 = cook.MakeSoupAsync();
Task<Sandwich> t3 = cook.MakeSandwichAsync();
Eat(await t1);
Eat(await t2);
Eat(await t3);

?

第一个是:

  • 厨师,请给我做沙拉
  • 在等待沙拉时,您有一些空闲时间来刷猫。完成后,哦,看,沙拉就做好了。 如果厨师在你刷猫的时候完成了沙拉,他们并没有开始做汤,因为你还没有提出要求
  • 吃沙拉。 厨师现在在你吃饭时处于空闲状态。
  • 厨师,请给我煮点汤。
  • 在等待汤时,您有空闲时间来清洁鱼缸。做完之后,哦,看,汤就做好了。 如果厨师在您清理鱼缸时煮完汤,他们不会开始做三明治,因为您还没有要求。
  • 喝汤。 厨师现在在你吃饭时处于空闲状态。
  • 厨师,请给我做个三明治。
  • 再说一遍,在等待的时候找点别的事情做。
  • 吃三明治。

你的第二个程序相当于:

  • 厨师,请给我做沙拉
  • 厨师,请给我煮点汤。
  • 厨师,请给我做个三明治。
  • 沙拉做好了吗?如果没有,在等沙拉的时候,你有一些空闲时间刷猫。 如果厨师在你刷猫的时候完成了沙拉,他们就会开始做汤
  • 吃沙拉。 当你吃饭的时候,厨师仍然可以做汤和三明治。
  • 汤做好了吗? ...

你看出区别了吗?在您的原始程序中,直到您吃完第一道菜,您才告诉厨师开始下一道菜。在您的第二个程序中,您预先要求所有三道菜,并在它们可用时按顺序食用它们。第二个程序更好地利用了厨师的时间,因为厨师可以“领先”你。

【讨论】:

  • 我认为需要注意的是厨师可以同时制作多道菜,这似乎没有明确说明。因此,在第 2 种情况下,当您要求所有 3 道菜时,厨师可能能够在汤在烤箱中蠕动时制作沙拉。这就是为什么在第一种情况下,让厨师一次做一件事是没有意义的,因为他们一次可以做多道菜。
  • @Letseatlunch:这是一个很好的观点,但我的论点没有必要;这里的相关点是,当您首先要求所有三件事时,服务提供商可以一次完成,或者一个接一个地完成,但至少他们知道完成所有三件事。如果您在第一个完成之前不要求第二个,那么您可能未充分利用资源。
【解决方案2】:

在第一个代码 sn-p 中,在第一个服务调用完成之前,您甚至不会开始第二个服务调用(同样在第二个服务调用完成之前不会开始第三个服务调用)。简而言之,它们是按顺序执行的。

在第二个 sn-p 中,您启动所有三个服务调用,但在所有三个都完成之前不要继续执行代码。简而言之,它们都是并行执行的。

如果第二个/第三个调用在获得前一个操作的结果之前无法开始,那么您需要执行第一个 sn-p 之类的操作才能使其正常工作。如果服务调用完全不相互依赖,那么出于性能原因,您希望它们并行执行。

如果出于某种原因,您真的不喜欢额外的局部变量,还有其他方法可以使用替代语法并行执行任务。与您的第二种选择类似的另一种选择是:

var Data = await Task.WhenAll(WebService1.Call(), 
    WebService2.Call(), 
    WebService3.Call());

【讨论】:

  • 代码不是停在等待Task1直到Task1完成,然后停在Task2并等待Task2等?
  • await 的线程将停止,但两个任务将继续进行。
  • @StephenCleary 是的,当你发表评论时,我已经在编辑它了。似乎我在 5 分钟前设法忍住了它。 ;)
  • @EricB 你指的是哪个sn-p?还要记住await 并不意味着等待。它没有阻塞线程。第一个 sn-p 在第一个任务的延续中启动第二个任务,然后在第二个任务的延续中启动第三个任务。另一个 sn-p 立即启动所有三个,然后在所有三个的 continuation 都运行后执行操作。
  • @EricB:WhenAll 会更高效,而且我个人认为它更好地捕获了所需的语义(“等待所有这些完成”而不是“等待第一个,然后是第二个,然后是第三个”)。
【解决方案3】:

Servy posted 一个很好的答案,但这里是使用Tasks 的视觉描述,以帮助说明问题所在。此代码的功能与您的不同(它不会执行所有同步上下文的操作,例如将控制权交还给消息泵),但它很好地说明了问题。

你的代码正在做这样的事情

var fooTask = Task.Factory.StartNew(Foo);
fooTask.Wait();
var fooResult = fooTask.Result;

var barTask = Task.Factory.StartNew(Bar);
barTask.Wait();
var barResult = barTask.Result;

var bazTask = Task.Factory.StartNew(Baz);
bazTask.Wait();
var bazResult = bazTask.Result;

并且更正后的代码正在做这样的事情

var fooTask = Task.Factory.StartNew(Foo);
var barTask = Task.Factory.StartNew(Bar);
var bazTask = Task.Factory.StartNew(Baz);

fooTask.Wait();
var fooResult = fooTask.Result;
barTask.Wait();
var barResult = barTask.Result;
bazTask.Wait();
var bazResult = bazTask.Result;

您可以看到所有 3 个任务都在运行,同时等待第一个结果返回,在第一个示例中,第二个任务直到第一个任务完成后才开始。

【讨论】:

    猜你喜欢
    • 2023-03-24
    • 2020-04-20
    • 1970-01-01
    • 2023-03-13
    • 1970-01-01
    • 2015-06-08
    • 2016-03-05
    • 2018-06-18
    • 2019-05-31
    相关资源
    最近更新 更多