【问题标题】:Practical usage of await on the same line as the async call?await 的实际用法与异步调用在同一行?
【发布时间】:2014-01-21 19:27:27
【问题描述】:

在同一行调用带有 await 的异步方法的目的是什么? (不是行为是什么,而是这种模式的实际用途是什么)。据我了解,这实际上是一个同步的“阻塞”调用,因为在此上下文中的执行在 GetDocumentAsync 完成之前不会继续,对吗?

Document someDocument = await db.GetDocumentAsync(documentId);
//AFAIK blocks until GetdocumentAsync completes and returns 
CalledAfterAwaitReturnsAndResultIsAvailable(someDocument.SomeProperty);

我见过的大多数展示如何调用异步方法的教程都是这样做的,我在其他人的代码中也看到过。对我来说,它违背了异步执行的目的。似乎从异步调用中受益,您将在没有等待的情况下进行异步调用,并执行一些不依赖于someDocument 的其他不相关的顺序工作(或启动其他一些异步调用),然后执行@987654326 @ 获取异步调用的结果,然后根据该返回执行任何操作。这样您的其他不相关处理就可以在异步调用也在另一个线程中工作时执行。上面的模式我看了这么多,我开始怀疑我是不是误解了什么。

以这种方式进行异步调用的实际应用是什么,它会阻塞直到完成?

【问题讨论】:

  • (这里没有反对意见)Await 方法不是同步阻塞调用。它们立即返回,将方法的其余部分包装在一个 Task 中(这非常简单,但你明白了),然后在等待的调用完成后恢复(在某个线程上,取决于)。
  • MSDN 包含大量解释 async 工作原理的文章,可以通过搜索您的帖子标题找到...例如 msdn.microsoft.com/en-us/library/hh191443.aspx
  • @AaronLS:你可能遇到的困难部分是理解,在某些时候,有人必须得到答案,因此被“阻塞”,直到调用树下的所有等待都完成.但这种想法是不正确的。例如,如果您在事件处理程序中异步调用 Web 方法,并等待该调用完成以更新 UI,那么实际上您有两个方法。一种启动进程(直到等待),立即返回,另一种完成调用(等待之后)并更新 UI。一切都发生在 UI 调度程序上,所以你不会注意到。
  • 如果你使用“同步阻塞调用”作为“非阻塞调用”它肯定会混淆读者。 “顺序执行的代码”和“这是阻塞同步调用”之间确实存在很大差异。我的假设是你的“阻塞直到完成”是完全错误的,因此需要 101 篇文章......显然这不是你的问题 - 抱歉。如果有兴趣,您可以查看weblogs.asp.net/dixin/archive/2012/11/02/… 之类的内部详细信息。
  • 关于标题...等待异步调用。这可能是一些不正确的术语。检查斯蒂芬图布的"Invoke the method with await"... ugh!

标签: c# asynchronous async-await


【解决方案1】:

await 关键字不会在任务上调用Wait,而是调用ContinueWith。结果是,您得到的不是阻塞调用,而是完成回调。

【讨论】:

    【解决方案2】:

    我认为您缺少的关键是,当您点击第一个 await 时,您所在的函数返回 Task<Document>(假设您的函数声明为 public async Task<Document> Foo())并让调用的人你的职能继续做其他工作。

    然后,调用者可以处理一些其他工作并稍后检查任务以查看它是否已完成,或者他可以自己立即await 并继续延迟工作,直到你遇到有工作要做的人(在 Forms 或 WPF 上,这一直到 Message Pump,让 UI 在您等待任务完成时保持响应)。

    【讨论】:

    • 所以每当我想让我自己的方法也支持异步调用时,都会使用这种模式的主要目的(与调用在同一行等待),对吗?
    • 正确,如果您正在执行异步,您可能希望调用方法也异步(以及该函数的调用方法和该函数的函数调用方法)“一直向下”以获得任何真实的好处。通常,如果您不能“一直向下”执行此操作,则只需坚持使用非异步版本的调用即可获得更好的性能(这在 ASP.NET 的异步使用中通常是正确的)
    • 不争辩,我已经看到“trickling async”并理解这一点,但在某些时候,为了实现这些好处,链上的某个人必须使用延迟等待来调用 async 方法,否则整个链的行为有些同步。 IE。在 MVC 中,如果我有一个控制器方法标记 async(从其他异步方法调用中提取),MVC 框架将调用我的操作异步方法而不等待(即延迟),以便立即释放请求处理程序/资源并且不要'不必等待操作方法返回。
    • 我将发布我认为更简洁的答案,对于我的其他人来说可能更容易理解,但您指出了我遗漏的关键内容,因此将其标记为答案。
    【解决方案3】:

    这种模式的实际应用是当您希望将异步功能传播/公开给您的方法的其他潜在调用者时。虽然在您的方法的上下文中,它会看起来表现得好像它是同步的,但它实际上并不是阻塞的。父调用者可以从子方法异步功能中受益,并在 GetDocumentAsync 处理时执行并行处理。

    public async Task<List<string>> ProcessDocument()
    {
      Document someDocument = await db.GetDocumentAsync(documentId);
      //Since await is on the same line as the async call, 
      //  Parse will not be called until GetDocumentAsync completes and result is available.
      //Thus it appears to "block", it is not technically blocking, 
      //  as a caller of ProcessDocument can start an async call to ProcessDocument 
      //  and perform other parallel processing while GetDocumentAsync is processing,
      //  because ProcessDocument is marked async
      return Parse(someDocument.Contents);
    }
    
    public async void ApplicationStart() {
    
        //no await when calling async method, processing begins asynchrounously
        Task<List<string>> parseDocTask = ProcessDocument();
    
        //we will continue other work while ProcessDocument is asynchronously performing work
        //We can execute DoIndependentWork while at the same time ProcessDocument is working on a separate thread asynchronously
        DoIndependentWork();
    
        //get result of async call, either will block until result is available,
        // or if async ProcessDocument task has already completed, will continue immediately
        List<string> parsedDocument = await parseDocTask;
    
        //do something with parsedDocument
    }
    

    【讨论】:

    • 另一种思考方式是async 方法是顺序的,而不是同步的
    • 一个性能提示,如果 Parse 不需要在同一线程上 GetDocumentAsync 当你进入函数时你可以做 db.GetDocumentAsync(documentId).ConfigureAwait(false) 让编译器把继续处理 ThreadPool.QueueUserWorkItem( 而不是使用 SynchronizationContext.Current.Post( 回调其余代码。
    • 这已经很老了,但我很惊讶没有人提到您不能在未标记为 async 的方法中使用 await 关键字。那不会编译。
    • @Michael 意识到我还返回了一个 List&lt;string&gt;,它应该是 Task&lt;List&lt;string&gt;&gt;,谢谢。
    【解决方案4】:

    该方法在逻辑上阻塞。不同的是,如果异步代码是阻塞IO操作,会返回当前线程。

    如果存在当前SynchronizationContext,则继续将发布到该SynchronizationContext(通常是UI 消息泵);否则,延续将被发布到线程池。

    这是对正在发生的事情的过度简化,但有助于深入了解。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-07-10
      • 1970-01-01
      • 2017-05-13
      • 1970-01-01
      • 2021-06-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多