【问题标题】:minimalist implementation of async await异步等待的极简实现
【发布时间】:2014-01-28 08:08:32
【问题描述】:

试图理解为什么 .NET 以他们的方式实现 async-await

当更改一段简单的代码以使用 async-await 时,似乎最不需要的工作就是使用 async-await 指令标记调用和被调用的方法:

private async void OnGuiClick(object sender, EventArgs args)
{
  textBox1.Text = await Work();
}

private async Task<string> Work()
{
  await Task.Delay(2000);
  return "Result";
}

为什么.NET 坚持两者? 即用一个关键字指定一个表达式会很好,必须立即在工作线程上异步评估表达式 - 而调用 GUI 线程被释放以执行其他任务,但将再次连接以执行剩余代码一次工作线程完成。

是否有更简单或更好的方法来分配工作线程来处理 Work 方法,然后自动(无需求助于 Invoke(...))确保相同的调用 GUI 线程处理结果? 为什么不这样:

private void OnGuiClick(object sender, EventArgs args)
{
  textBox1.Text = <'some async directive'> Work();
}

private string Work()
{
  Thread.Sleep(2000);
  return "Result";
}

(MSDN documentation 声明如果目标不包含 await 语句,编译器将同步执行代码 - 但那有什么意义呢? - 当然 await async 关键字仅用于异步使用。那为什么要这样做复杂,而不是使用单个指令?)

【问题讨论】:

  • Eric Lippert 不久前发布了一篇不错的blog post,可能值得一读,也就是说:“方法上的“异步”修饰符并不意味着“此方法会自动安排在工作线程异步””和“异步方法的全部意义在于您尽可能地留在当前线程上。”
  • 谢谢 - 那篇文章真的很有帮助:await 不创建工作线程,它使用当前线程。而且 async 不保证异步实现(即使编译器会发出警告),由开发人员确保 async 方法正确异步。
  • @StephenCleary,部分同意 - 但我真正的问题与指示的 SO 主题不同。本主题中的其他建议和解释很有帮助,但我真正想要的在下面对您的回答的评论中有所描述

标签: c# asynchronous async-await


【解决方案1】:

你提出了一个很好的例子——我相信编译器只需要“await”关键字。我不认为它关心“异步”修饰符。

然而,编译器坚持你用“async”修饰符标记你的方法是一件好事 - 它允许你检查一个方法是否会同步/异步运行而不必深入研究其实施。它还告诉您是否必须/能够await 它。我相信这些是强制使用这两个关键字的原因,而不仅仅是一个。

类似的情况是:为什么您需要使用“public”访问修饰符标记接口实现方法,即使它不能是私有/内部/受保护/受保护的内部?看起来是多余的吧?但是,它确实增加了可读性。

【讨论】:

  • 我的印象是添加它是为了防止破坏现有代码......如果你有一个名为 await 的变量,你可能会遇到各种问题!
  • @dcastro,同意,您提到的原因是有道理的,这些限制提供了价值。不过,我真正(通常不是)想要的是一个简单的指令,如上所述。即我什至不在乎被调用的方法是否是异步/异步......我告诉编译器无论如何都要在另一个工作线程上运行它,然后再连接相同的调用 GUI 线程。真的没有简单的方法可以做到这一点吗?
  • @Fortmann 最简单的方法是await Task.Run(...);,这并不复杂。另外,请注意await 关键字并不一定意味着被调用的方法在另一个线程上运行。如果方法返回的Task 已经完成,那么await 将简单地解包结果(如果有)并继续,而不暂停当前线程。例如,该方法可能返回Task.FromResult("hi");
  • 谢谢 - 我错过了。类似'statusStripLabel.Text = await Task.Run(() => { return LongRunningTaskThatReturnsString(); });'看起来是最简单的解决方案。
  • 我不同意最后一段。在每个界面成员前面放同一个词并不能改善任何事情。
【解决方案2】:

我有一个关于这个主题的blog post。主要原因是为了向后兼容(允许 await 关键字成为 contextual 关键字)。阅读代码时,将async 关键字作为指标也很好。

另请参阅有关该主题的 Eric Lippert's blog post,以及他的 comments on another postChannel9MSDN forumsright here on SO

【讨论】:

  • 您的博客很好,并解释了为什么某些替代方案不起作用(尤其是 await 和 async 的推断)。更具体地说,我同意 Adelphus 从 SO 主题的推理 - 我更喜欢单个简化指令 此外(替代)异步/等待。确切地知道它将做什么以及含义是什么(请参阅我的问题的第二个代码块)。
  • async 并不意味着线程。完全没有。如果您想在后台线程上工作,然后同步回 UI 线程,那么只需执行await Task.RunTask.Run 确实在后台线程上工作,await 允许 UI 线程(异步)等待该工作。
  • @Fortmann:您可能会发现我的async intro 很有帮助。
  • 谢谢,你的博客很棒——是的,我了解异步是如何工作的(现在)。一个问题是(1)异步是不直观的,(2)除了如上所述的 async/await 之外,还需要另一个指令。 “异步”(根据定义)意味着“不同时”,在软件开发的历史背景下,它肯定意味着并发处理——这就是为什么它对大多数人来说是不直观和误导的。
  • @Fortmann:async 是并发的。但是,它不是并行。这就是区别所在。并行处理是并发的一种形式,异步处理是并发的另一种形式。我同意术语“异步”多年来一直(误用)来表示“在后台线程上运行”(例如,关于“异步委托”的 MSDN 文档),但没有多少其他术语适合。 ..
【解决方案3】:

指定async 关键字直接暗示该方法的内部必须从头开始重写以支持async/await 模型。您可能知道,生成的 IL 看起来与您的原始方法不太一样。您可能会争辩说这种重写也可能是隐含的,但在某种程度上明确允许这种情况发生是有意义的。

另一方面,你有一个正确的观点:当使用 yield 关键字隐式实现 IEnumerable 时会发生类似的事情,这也与 async 模型有点相似,因为它也创建了一个状态机。并且不存在在此类方法之前放置 enumerated 关键字的要求。

【讨论】:

  • 我缺乏理解: await 本身与分配工作线程无关。 async 关键字对我来说有点误导,因为它不保证异步操作(异步方法的开发人员对此负责)。如果解释正确,我理解为什么显式标记是好的,即显式标记某些东西是好的,因为它为调用者提供了更多信息
猜你喜欢
  • 1970-01-01
  • 2018-04-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-26
  • 2018-02-13
  • 1970-01-01
相关资源
最近更新 更多