关于c#:为什么一直使用Async / await

Why use Async/await all the way down

我想澄清一下一直使用Await和Async的附加好处是什么。

如果我的应用程序正在调用await Func1()(因此此处不会阻塞UI)。并且Func1正在调用await Func2(),但是Func2()的结果对于Func1完成其工作很重要,那么为什么我需要使Func2()处于等待状态。 Func1()执行将花费相同的时间,因为它正在等待Func2完成。在这里等待的所有操作都增加了StateMachine开销。

我在这里想念什么吗?


一个更好的口号是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
}


主要优点是,等待异步方法会将工作线程返回到要在其他调用中使用的池(例如,对.NET MVC Web应用程序的Web请求)。异步工作在IO完成线程上完成。当等待的方法完成时,另一个工作线程将收集结果并恢复执行。这样可以防止工作线程池耗尽,并允许您的应用程序处理更多负载(取决于CPU,内存和网络吞吐量)。

至于"一直等待",对我来说似乎是一个问题。通常,await与应用程序必须等待的外部资源(数据库调用,HTTP请求等)相关联。如果您等待不具有外部IO依赖关系的代码,则会创建不必要的开销。 async方法链中可能有多个awaits,但是等待一些本身调用await但没有其他外部IO依赖性的代码并不好,只会增加回调/编译器开销。


通常,async / await背后的原因是相反的:

无论出于何种原因,您都决定使用await更容易编写Func2。因此,您可以通过添加所需的await来简化方法,这意味着您还必须更改方法签名(现在将包括async关键字和返回类型TaskTask<T>)。

由于返回类型已更改,因此您不再像以前那样(var result = Func2();)可以调用Func2,因此现在需要更改调用方法Func1。适应Func1的最简单方法通常是将其也设置为asyncawait Func2()

然后,出于相同的原因(更改了签名),您将需要将所有调用更改为Func1,依此类推,直到到达某种类型的入口点(UI事件处理程序或Main方法或其他方法)。

因此,您不必开始制作"外部"方法async,而是继续执行"内部"(称为)方法。您通常沿相反的方向(从"最内层"方法回到调用方法)制作事物async。其他答案将此想法称为"一直异步"。


如果您不等待异步方法,则会发生"此异步方法缺少'await'运算符,将同步运行"。要利用异步方法的好处,您必须等待它,将调用方转变为可以等待的异步方法,等等。

从流程图中,您可以看到async方法返回一个Task(将在将来完成工作的承诺)并向其调用者提供控制权。此时,呼叫者可以继续进行不依赖于此结果的工作。显然,这需要使调用堆栈冒泡,以找到可以在没有结果的情况下完成的所有有收益的工作(在UI应用程序中,该工作将包括解锁UI,这就是为什么它一直异步到事件处理程序的原因)在以下示例中)。

来自我最初的误读。因此,您已经找到了一些需要调用的异步代码,是否值得将异步等待模式扩展到您的代码中:

我已经听到很多东西,异步等待的主要问题是它实际上的语法太简单了。程序流程变得复杂。我真的很喜欢异步等待,但是不幸的是,在大多数异步代码中,我所看到的这是不值得的,并且不必要地破坏了我的调用堆栈。

要记住的一件好事是50ms规则。

"这是Microsoft遵循WinRT API的规则;任何占用时间少于50ms的东西都被视为"快速",并且足够接近"即时",以至于它们不需要异步API。"铅>

这是在鼓励异步等待的上下文中使用的。但是,我认为应该同样地告诉使用async-await的开发人员使用本质上直接的功能来将其删除。

https://msdn.microsoft.com/en-us/cc300389.aspx#E


在基于UI的应用程序中,异步等待提供了一种非常简洁的方法来处理导致UI更新的异步调用。如果UI中的"顶级"处理程序正在等待结果,那么除非这样做是合情合理的,否则在整个链中进行相同的操作可能没有真正的好处。 async await的设计目标是使异步编程看起来更加同步和连续,并且不分散回调等,从而使异步编码更易于评估。您不必在任何地方都使用它。