您为什么要“等待”一个方法,然后立即查询它的返回值?


24

本文中,提供了以下示例代码(为简便起见,对其进行了略作编辑):

public async Task<ActionResult> Details(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }

    Department department = await db.Departments.FindAsync(id);

    if (department == null)
    {
        return HttpNotFound();
    }

    return View(department);
}

FindAsync方法Department通过其ID 检索对象,并返回Task<Department>。然后立即检查部门以查看其是否为空。据我了解,以这种方式要求Task的值将阻止代码执行,直到返回了来自waited方法的值,从而有效地使它成为同步调用。

你为什么要这样做?Find(id)如果您要立即阻止,仅调用sync方法不是更简单吗?


这可能与实现有关。... else return null;然后,您需要检查该方法是否确实找到了您要的部门。
加里·杰里米(Jeremy Kato),

我没有看到任何一个asp.net,但在destop应用程序,通过做这种方式,你不冻结UI
雷米

这是一个从设计师那里解释等待概念的链接。msdn.microsoft.com/en
Jon Raynor

如果线程接触开关使您的速度变慢,或者许多线程堆栈的内存使用对您来说是一个问题,那么仅使用ASP.NET可以考虑使用Await。
伊恩

Answers:


24

据我了解,以这种方式要求Task的值将阻止代码执行,直到返回了来自waited方法的值,从而有效地使它成为同步调用。

不完全的。

调用时await db.Departments.FindAsync(id),任务被发送出去,当前线程返回到池中,以供其他操作使用。执行流被阻止(department如果我理解正确,那么无论以后是否使用,都可以),但是当您等待操作在机外完成时,线程本身可以被其他事物自由使用。由事件或完成端口发出信号)。

如果您调用d.Departments.Find(id)了该线程,那么即使大多数处理是在DB上完成的,线程也确实坐在那里等待响应。

绑定磁盘后,您可以有效地释放CPU资源。


2
但是我认为await所做的只是将方法的其余部分作为在同一线程上的继续进行签名(有例外;某些异步方法将其自身的线程旋转起来),或者将async方法作为在同一线程上的继续进行签名,并允许其余要执行的代码(如您所见,我不清楚如何async工作)。什么你所描述听起来更像是一个复杂的形式Thread.Sleep(untilReturnValueAvailable)
罗伯特·哈维

1
@RobertHarvey-它确实将其分配为延续,但是一旦您发送了要处理的任务(及其延续),就没有任何可运行的东西了。除非您指定它(通过ConfigureAwaitiirc),否则不能保证它位于同一线程上。
泰拉斯汀

1
看到我的答案...默认情况下,延续被编组回原始线程。
迈克尔·布朗

2
我想我在这里看到了我所缺少的。为了await使它提供任何好处,ASP.NET MVC框架必须对其进行调用public async Task<ActionResult> Details(int? id)。否则,原始呼叫将被阻塞,等待department == null解决。
罗伯特·哈维

2
@RobertHarvey await ...“返回”时,FindAsync呼叫已结束。那就是等待的事情。之所以称为await,是因为它使您的代码等待事情。(但请注意,这与使当前线程等待事物不同)
user253751 '16

17

我真的很讨厌没有一个示例显示如何在等待任务之前等待几行。考虑一下。

Foo foo = await getFoo();
Bar bar = await getBar();

Console.WriteLine(“Do some other stuff to prepare.”);

doStuff(foo, bar);

这是示例所鼓励的代码,您是对的。这没有什么意义。它确实释放了主线程来做其他事情,例如响应UI输入,但是async / await的真正功能是在等待潜在的长时间运行任务完成时,我可以轻松地继续做其他事情。上面的代码将“阻止”并等待执行打印行,直到获得Foo&Bar。无需等待。我们可以在等待时进行处理。

Task<Foo> foo = getFoo();
Task<Bar> bar = getBar();

Console.WriteLine(“Do some other stuff to prepare.”);

doStuff(await foo, await bar);

现在,有了重写的代码,我们不必停止并等待我们的值,直到必须这样做为止。我一直在寻找这些机会。明智地等待我们的时间可以显着提高性能。这些天我们有多个核心,不妨使用它们。


1
嗯,这与内核/线程无关,而与正确使用异步调用有关。
Deduplicator

您没看错@Deduplicator。这就是为什么我在答案中引用“阻止”。在不提及线程的情况下很难讲这些东西,但是尽管仍然承认其中可能涉及多线程。
RubberDuck

我建议您在等待另一个方法调用时要小心。有时编译器会误解这种等待,您会得到一个非常奇怪的异常。在所有异步/等待只是语法糖之后,您可以使用sharplab.io检查生成的代码的外观。我曾几次观察到这种情况,现在我只是在需要结果的电话上方等待一行...不需要那些麻烦。
evictednoise

“这没有什么意义。” -这很有道理。“继续做其他事情”适用于完全相同的方法的情况并不常见。通常,您只想await让线程做完全不同的事情。
塞巴斯蒂安·雷德尔

当我说“这没什么意义” @SebastianRedl时,我的意思是显然可以并行运行两个任务,而不是运行,等待,运行,等待。这比您似乎想像的要普遍得多。我敢打赌,如果您环顾代码库,就会发现机会。
RubberDuck

6

因此,这里的幕后还有更多事情发生。异步/等待是语法糖。首先看一下FindAsync函数的签名。它返回一个任务。您已经看到了关键字的魔力,它将该任务拆箱到部门中。

调用功能不会阻塞。发生的情况是,对部门的分配以及紧随await关键字之后的所有内容都装在一个闭包中,并出于所有意图和目的传递给Task.ContinueWith方法(FindAsync函数自动在另一个线程上执行)。

当然,在幕后还会发生更多的事情,因为该操作被编组回原始线程(因此,在执行后台操作时,您不必再担心与UI同步),并且在调用函数为Async的情况下(并被异步调用)。

因此,发生的事情是您获得了Async操作的魔力,而没有陷阱。


1

不,它不会立即返回。等待使方法调用异步。调用FindAsync时,Details方法将返回未完成的任务。当FindAsync完成时,它将把结果返回到Department变量中,并继续执行Details方法的其余部分。


不,根本没有阻塞。直到异步方法完成后,它才可以进入部门== null。
史蒂夫

1
async await通常不会创建新线程,即使创建了新线程,您仍然需要等待该部门值来查找它是否为空。
罗伯特·哈维

2
因此,基本上,您要说的是,为了使此功能毫无益处,public async Task<ActionResult> 必须同时await
罗伯特·哈维

2
@RobertHarvey是的,您已经知道了。异步/等待本质上是病毒式的。如果“等待”一个函数,则还应该等待任何调用它的函数。await请勿与.Wait()或混合使用.Result,因为这可能导致死锁。异步/等待链通常以带有async void签名的函数结束,该签名通常用于事件处理程序或UI元素直接调用的函数。
KChaloux

1
@Steve-“ ...因此它将在其自己的线程上。 ”不,读取Task 仍然不是线程,并且async不是并行的。这包括实际输出,表明未创建新线程。
制造商史蒂夫

1

我喜欢将“异步”视为一个合同,该合同说“我可以在需要时异步执行它,但也可以像其他任何同步函数一样调用我”。

意思是,一个开发人员正在制作函数,并且一些设计决策使他们将一堆函数标记为“异步”。函数的调用者/消费者可以自由决定使用它们。就像您说的那样,您可以在函数调用之前立即调用await并等待它,这样您就将其视为同步函数,但是如果您愿意,则可以在不等待的情况下调用它

Task<Department> deptTask = db.Departments.FindAsync(id);

然后在您调用的函数下移10行

Department d = await deptTask;

因此将其视为异步函数。

由你决定。


1
这更像是“我保留异步处理消息的权利,可以使用它来获取结果”。
Deduplicator

-1

“如果您仍然要立即阻止”,则答案为“是”。仅当您需要快速响应时,等待/异步才有意义。例如,UI线程进入了一个真正的异步方法,UI线程将返回并继续监听按钮单击,而“ await”下面的代码将被其他线程执行,并最终得到结果。


1
这个答案提供了什么,而其他答案没有提供?
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.