我想澄清一下一直使用Await和Async的附加好处是什么。
如果我的应用程序正在调用await Func1()(因此此处不会阻塞UI)。并且Func1正在调用await Func2(),但是Func2()的结果对于Func1完成其工作很重要,那么为什么我需要使Func2()处于等待状态。 Func1()执行将花费相同的时间,因为它正在等待Func2完成。在这里等待的所有操作都增加了StateMachine开销。
我在这里想念什么吗?
- 如果Func2()正在等待外部资源:保持线程没有好处...
一个更好的口号是async。因为您从异步操作开始,并使它的调用者异步,然后使下一个调用者等。
当您具有固有的异步操作(通常是I / O,但不一定是)并且您不想浪费线程闲置地等待操作完成时,应该使用async-await。选择async操作而不是同步操作不会加快操作速度。这将花费相同的时间(甚至更多)。它只是使该线程能够继续执行其他一些CPU约束的工作,而不是浪费资源。
但是要能够await进行该操作,该方法必须是async之一,而调用方需要将其await依次类推,以此类推。
因此async一直使您能够实际进行异步调用并释放任何线程。如果不是完全async,则某些线程被阻塞。
所以,这个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| async Task FooAsync()
{
await Func1();
// do other stuff
}
async Task Func1()
{
await Func2();
// do other stuff
}
async Task Func2()
{
await tcpClient.SendAsync();
// do other stuff
} |
比这更好:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| void Foo()
{
Func1();
// do other stuff
}
void Func1()
{
Func2().Wait(); // Synchronously blocking a thread.
// do other stuff
}
async Task Func2()
{
await tcpClient.SendAsync();
// do other stuff
} |
- 因此,除非您一直等待,否则没有任何好处。
-
因此,我尝试了Func2().Wait();并冻结了执行(尽管async-await没问题)。我不确定我是否遵循它,但是还有其他选择吗?我的意思是,如果我要等待,为什么还要使其异步?
主要优点是,等待异步方法会将工作线程返回到要在其他调用中使用的池(例如,对.NET MVC Web应用程序的Web请求)。异步工作在IO完成线程上完成。当等待的方法完成时,另一个工作线程将收集结果并恢复执行。这样可以防止工作线程池耗尽,并允许您的应用程序处理更多负载(取决于CPU,内存和网络吞吐量)。
至于"一直等待",对我来说似乎是一个问题。通常,await与应用程序必须等待的外部资源(数据库调用,HTTP请求等)相关联。如果您等待不具有外部IO依赖关系的代码,则会创建不必要的开销。 async方法链中可能有多个awaits,但是等待一些本身调用await但没有其他外部IO依赖性的代码并不好,只会增加回调/编译器开销。
通常,async / await背后的原因是相反的:
无论出于何种原因,您都决定使用await更容易编写Func2。因此,您可以通过添加所需的await来简化方法,这意味着您还必须更改方法签名(现在将包括async关键字和返回类型Task或Task<T>)。
由于返回类型已更改,因此您不再像以前那样(var result = Func2();)可以调用Func2,因此现在需要更改调用方法Func1。适应Func1的最简单方法通常是将其也设置为async和await Func2()。
然后,出于相同的原因(更改了签名),您将需要将所有调用更改为Func1,依此类推,直到到达某种类型的入口点(UI事件处理程序或Main方法或其他方法)。
因此,您不必开始制作"外部"方法async,而是继续执行"内部"(称为)方法。您通常沿相反的方向(从"最内层"方法回到调用方法)制作事物async。其他答案将此想法称为"一直异步"。
-
所以那就像癌症。
-
@matao:如果您认为async / await是一件坏事,那么也许是的。我曾经认为,它比旧的替代异步编程模式能产生更好的代码,因此从我的angular来看,将其与疾病进行比较是不必要的。当然,并非一开始就应该一切都不是异步的。在很多情况下,引入异步/并发根本没有多大意义,因此async / await本身不会散布到各处。
-
(太长时间了,所以不得不将其分解)[1]当然,我有点夸张了,但是在我看来,如果您不小心,那么很容易导致代码库中的每个调用异步/等待。 ,特别是如果您的初级开发人员不了解它的用途以及何时不使用它。
-
[2]我认为这是一个泄漏的抽象,并且它不是一个足够高级别的工具来正确处理并发。例如,如果我使用的是存储库,则无论代码是由Web服务,数据库还是内存中的字典支持,我都不会在代码级(尽管在体系结构级)。但是Web服务会将异步/等待泄漏回到我的调用堆栈中。另外,数据库调用也通过网络进行,但是EF5(正确)不会强制您使用异步/等待。
-
[3]如果我要触发多个请求并让.Join()等待所有请求完成怎么办? myabe我遗漏了一些东西,但是用这个成语似乎并不简单。我不知道,我认为I / O阻塞线程应该能够由直接调用者处理,因此它对大多数代码都是透明的。
-
@matao:我认为评论部分没有足够的空间来讨论async / await的一般优缺点,也许进行聊天会更合适。简而言之:(1)我怀疑async / await主要是为了帮助程序员创建响应式UI而引入的。在早期的.NET中,您必须进行显式的多线程和回调,这并不容易。许多人弄错了这一点(即在更新UI控件之前没有回到UI线程)。 async / await使这种情况变得容易得多。但是实际上,它并不涵盖所有的多线程方案,例如您的[3]。
-
(2)鉴于您的[1]和特别是。 [2],如果您真的不想关心是实现同步还是异步,那么对我而言,合乎逻辑的结论是,通过使表面上的所有内容异步来隐藏实现细节。对于ValueTask<T>,这是可行的,因为struct(与Task<T>不同)非常轻巧,因此在实际上完全阻塞/同步的方法中不会有太多开销。但是,是的,设计一个100%异步的API(即使不是必需的)也是恕我直言。
-
(3)理论上,您可以通过创建一个表示Task s元组的await -able类型在多个Task上实现.Join操作。实际上,您需要两种这样的元组类型,一种类型的等待者在功能上等同于Task.WhenAny,另一种在功能上等同于Task.WhenAll。
-
最后,我同意async / await并不是完美的,但是对于许多常见的编程任务来说,这是我们迄今为止在.NET中最简单,最简洁的编程模式。
-
公平地说,我认为对于最低公分母的开发,它在大多数情况下可能运行良好。我个人不喜欢它,更喜欢直接像TPL这样使用并发库。但我可以忍受。干杯!
如果您不等待异步方法,则会发生"此异步方法缺少'await'运算符,将同步运行"。要利用异步方法的好处,您必须等待它,将调用方转变为可以等待的异步方法,等等。
从流程图中,您可以看到async方法返回一个Task(将在将来完成工作的承诺)并向其调用者提供控制权。此时,呼叫者可以继续进行不依赖于此结果的工作。显然,这需要使调用堆栈冒泡,以找到可以在没有结果的情况下完成的所有有收益的工作(在UI应用程序中,该工作将包括解锁UI,这就是为什么它一直异步到事件处理程序的原因)在以下示例中)。
来自我最初的误读。因此,您已经找到了一些需要调用的异步代码,是否值得将异步等待模式扩展到您的代码中:
我已经听到很多东西,异步等待的主要问题是它实际上的语法太简单了。程序流程变得复杂。我真的很喜欢异步等待,但是不幸的是,在大多数异步代码中,我所看到的这是不值得的,并且不必要地破坏了我的调用堆栈。
要记住的一件好事是50ms规则。
"这是Microsoft遵循WinRT API的规则;任何占用时间少于50ms的东西都被视为"快速",并且足够接近"即时",以至于它们不需要异步API。"铅>
这是在鼓励异步等待的上下文中使用的。但是,我认为应该同样地告诉使用async-await的开发人员使用本质上直接的功能来将其删除。
-
异步等待的好资源在这里-blog.stephencleary.com/2012/02/async-and-await.html
-
@ShaunWilde很棒的资源。也没有什么像斯蒂芬·图卜
-
@ShaunWilde那是因为这与问题无关。即使是正确的,也只是一小段文字。
在基于UI的应用程序中,异步等待提供了一种非常简洁的方法来处理导致UI更新的异步调用。如果UI中的"顶级"处理程序正在等待结果,那么除非这样做是合情合理的,否则在整个链中进行相同的操作可能没有真正的好处。 async await的设计目标是使异步编程看起来更加同步和连续,并且不分散回调等,从而使异步编码更易于评估。您不必在任何地方都使用它。