【问题标题】:What happens when you await an asynchronous task in C#?在 C# 中等待异步任务时会发生什么?
【发布时间】:2019-04-29 03:55:54
【问题描述】:

根据我在C#中所有async/await教程中的理解,我知道当我们定义一个async方法时,这意味着我们可以在其中添加await关键字,并以一个任务作为参数,使方法返回到如果任务太长,调用者。该方法可以在任务完成后恢复。

public void MyMainMethod()
{
   DoSomethingAsync();
   KeepOnDoingOtherStuff();
}

public async Task DoSomethingAsync()
{
   //Do whatever
   await LongOperation();
   // Rest of the method that is executed when the long operation is 
    //finished.

}

所以在这里,当长操作正在进行时,当它等待时,DoSomething 异步将手交给 MainMethod 并执行 KeepOnDoingOtherStuff。好的

现在我的问题是我的 DoSomethingAsync 本身是否正在等待。在这种情况下:

public async void MyMainMethod()
{
   await DoSomethingAsync();

   //Here is a critical method that I want to execute only when 
     //DoSomethingAsync is finished

   CriticalStuff();
}

public async Task DoSomethingAsync()
{
   //Do whatever
   await LongOperation();
   // Rest of the method that is executed when the long operation is 
    //finished.

}

现在我等待我的 DoSomethingAsync 以确保它不会更进一步,但问题是 DoSomethingAsync () 在等待 LongOperation() 时仍会交给调用者,并且我的 Main 方法会恢复,这是我不想要的,因为它执行了我的关键任务。

在异步逻辑中有一些我很怀念的东西,那么“等待”一个包含异步方法的方法又有什么意义呢?

非常感谢我的问题的解决方案。

提前致谢。

【问题讨论】:

  • 在第二个示例中,在 DoSomethingAsync() 完全完成之前不应调用 CriticalStuff()。您在此处发布的示例代码中一定缺少某些内容。
  • 这是一篇关于异步编程的好文章,如果您有兴趣阅读有关此主题的更多信息。 rubikscode.net/2018/05/28/…
  • 你的 MyMainMethod() 应该返回一个 Task 以及任何应该等待的调用
  • "问题是 DoSomethingAsync() 在等待 LongOperation() 时仍然给调用者,而我的 Main 方法恢复" 它不应该...其中的await 应该注意这一点,与LongOperation 上的await 相同。正如@Stijn 所说,如果发生这种情况,那么您的代码中一定还有其他事情在您没有显示。
  • 只有在 DoSomethingAsync 完成后在您发布的代码中才会发生。不是吗?请为您的问题创建一个Minimal, Complete, and Verifiable example

标签: c# .net asynchronous


【解决方案1】:

我正在讨论您的代码的这种变体:

public async void MyMainMethod()
{
   await DoSomethingAsync();

   //Here is a critical method that I want to execute only when 
     //DoSomethingAsync is finished

   CriticalStuff();
}

public async Task DoSomethingAsync()
{
   //Do whatever
   await LongOperation();
   // Rest of the method that is executed when the long operation is 
    //finished.

}

DoSomethingAsync 返回时,它不只是盲目地将控制权交还给调用方法。它给它一个Task。如果该方法实际上是同步完成的,它将交还一个已经完成的任务。否则,如果该方法仍有工作要做,Task 将运行,而不是(尚未)完成。

所以,DoSomethingAsync 实际上会触发一些异步行为并将尚未完成的Task 返回到MainMain 用它做什么?它awaits 它。 await 是该方法如何指示“除非这个可等待的(通常是 Task)完成,否则我无法做任何有用的事情”。

如果(如这里),Task 尚未完成,它不会取得任何进一步的进展(本身),直到发生。如果这是方法中的第一个(必需)await,则此时异步机制启动,注册一个延续并从 Main 本身返回。

在您作为await 参数提供的Task(或其他可等待的)完成之前,它不会越过任何await 行,并且对于使用async 机制的方法,直到那个方法完成。

(我忽略了上面描述中的异常故事。这都是快乐的事情)

【讨论】:

  • 所以当你说 Main“在 DoSomethingAsync 完成之前不能做任何有用的事情”时:我们什么时候可以说正式完成?因为在这个任务中还有另一个任务正在运行并正在等待(等待 LongOperation())。如果我应用逻辑:在 DoSomethingAsync 我点击了等待的 LongOperation() -> 它还没有完成所以我们回到调用者--> 这是 Main 和 await DoSomethingAsync() 之后的内容 ....(这是我特别不想要的,因为还没有完成..)。这就是我的困惑的来源......
  • @Zeppelin - 我会尝试用另一种方式表达它 - 每当您点击 await 并且实际上必须等待时,Task 表示完成此方法调用 仍标记为正在运行,而不是完成。在Task 转换为Complete 之前,您的方法不被认为是完整的。而你的呼叫者可以反过来await打开Task。有更清楚的吗?
  • 所以在示例情况下:虽然 LongOperation() 任务尚未转换为 Complete,但我的 DoSomethingAsync 将无法转换为完成,对吗?
  • 正确。使用这种代码,很容易发现您有多个Tasks,每个代表调用堆栈中每个异步方法的待处理活动。
  • 那么使 DoSomethingAsync 异步的意义何在?我可以同步调用 LongOperation(),它不会改变任何东西吗?当我的 LongOperation 未完成时,调用者无法继续。我看不出在等待本身的任务中等待任务的意义。
【解决方案2】:

我认为这里的不匹配来自这些话:

如果任务太长,让方法返回给调用者。

await 不是关于 long,确切地说。这是关于异步的。如果某些事情需要很长时间但发生在本地 CPU 上,那么可以肯定:它不太适合 await(尽管您可以使用 await 机制作为推动事情的便捷方式到工作线程,然后在完成后重新加入同步上下文线程)。

不过,更好的例子是任务是外部;它可能是对 SQL 数据库、redis 或通过 http 或通过某种 IO 层或等待套接字数据的另一个进程的调用。请注意,即使与物理本地 SSD 存储通信也可能需要长时间等待,至少从 CPU 速度来看是这样。当本地线程除了阻塞什么都不做(等待回复)时,那么:在许多情况下,线程可以做的事情要好得多。

对于客户端应用,这些“更好的东西”包括绘制 UI 和响应输入。对于服务器应用程序,那些“更好的东西”包括为其他并发请求/负载提供服务。


以这种方式衡量时:await 是编写需要来自外部源的数据的代码的有效方式,但不想在数据到达时阻塞当前线程。如果这不是您的情况:await 可能不适合您。

【讨论】:

  • 我明白了。但对我来说,DoSomethingAsync 异步的意义在于它可以在执行长时间操作时返回给调用者。但是,“调用者”正在等待 DoSomethingAsync 任务,因此它在未完成时不会执行任何操作。因此我可以使 DoSomethingAsync 同步。换句话说,为什么我们等待的任何任务都是异步的?
  • @Zeppelin 你在谈论并发,而我在谈论异步 - 相关但不同。我主要关注异步性,在这种情况下,您忽略的关键是 线程很昂贵,并且在许多情况下,您的线程可能有用,而不是阻塞。就您所说的并发而言:当然,在某些情况下您可以有效地使用 async 来协调 2 个(或更多)活动,并且:这是完全有效的。只是...不要await,直到您准备好“加入”(在线程方面)
  • @Zeppelin 把它放到上下文中:我运行的其中一个东西是一个 web-socket 服务器,它为每个实例提供大约 60k 个 TCP 连接。我无法运行 60k 线程(嗯,120k 线程 - 每个套接字读+写);历史悠久的异步 IO API ......很难做到正确;可行,但真的很难。但是:使用async/await 获得相同的结果:微不足道 - 非常简单的代码。 await 没有什么是你不能用更丑、更难、更错误的代码做的。但是await 使得它轻松搞定
猜你喜欢
  • 2012-09-11
  • 2014-11-22
  • 2014-09-06
  • 1970-01-01
  • 2022-07-25
  • 1970-01-01
  • 2018-12-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多