如果我等待我正在执行 ReadAsStringAsync() 的响应,我应该等待 ReadAsStringAsync() 吗?

Posted

技术标签:

【中文标题】如果我等待我正在执行 ReadAsStringAsync() 的响应,我应该等待 ReadAsStringAsync() 吗?【英文标题】:Should I await ReadAsStringAsync() if I awaited the response that I'm performing ReadAsStringAsync() on? 【发布时间】:2015-01-28 21:56:34 【问题描述】:

如果我等待我正在执行ReadAsStringAsync() 的响应,我是否应该等待 ReadAsStringAsync()?为了进一步澄清,以下之间有什么区别或正确方法?它们实际上是一样的吗?

var response = await httpClient.GetAsync("something");
var content = await response.Content.ReadAsStringAsync();
return new AvailableViewingTimesMapper().Map(content);

var response = await httpClient.GetAsync("something");
var content = response.Content.ReadAsStringAsync();
return new AvailableViewingTimesMapper().Map(content.Result);

【问题讨论】:

如果你想在使用结果之前对任务做一些事情,我认为你只会做var content = response.Content.ReadAsStringAsync(); 你永远不应该这样做Map(content.Result)你可能会死锁你的程序,如果你要推迟它,你仍然需要使用await。 Map(await content), 【参考方案1】:

你的第一个例子是正确的。第二个示例在异步操作期间不产生。相反,通过获取content.Result 属性的值,可以强制当前线程等待异步操作完成。

此外,正如评论者 Scott Chamberlain 指出的那样,通过阻塞当前线程,您可能会引入死锁的可能性。这取决于上下文,但await 的常见场景是在 UI 线程中使用该语句,并且 UI 线程需要保持对各种需求的响应,但包括能够实际处理完成等待操作。

如果你避免第二种模式,即从你不知道已经完成的Task 中检索Result 属性的值,你不仅可以确保有效地使用你的线程,还可以防止这种情况发生常见的死锁陷阱。

【讨论】:

作为上一段的扩展,等待已经完成的Task不需要额外的开销,它只会同步返回已经可用的结果。如果您使用的方法已标记为 async,则没有理由在任务上调用 .Result 而不是 await 我希望看到@ScottChamberlain 在此处给出的其他评论添加到答案中,因为它可能是 OP 所获得的最佳信息。他们的示例引起了对 Result 阻塞的混淆(因此这个答案的大部分内容),但他们仍然需要等待他们已经等待的 Response 上的方法。你不应该担心等待一个可等待的;最糟糕的情况是您立即得到结果,这听起来像是积极的。【参考方案2】:

ReadAsString之所以是async方法,是因为实际读取数据是一个IO操作。即使您已经有了 http 结果,内容也可能没有完全加载。不涉及额外的线程或大量计算。

HttpClient.GetAsync 允许您添加 HttpCompletionOption 以使 GetAsync 仅在整个 HttpResult 加载后返回。在这种情况下,HttpContent.ReadAsStringAsync 将同步完成(所谓的快速路径),因为内容已经存在。

所以你绝对应该等待它。

另外:由于这可能是不依赖于 UI 线程返回的库代码,您应该将 .ConfigureAwait(false) 添加到所有等待的方法调用中。

【讨论】:

“这是库代码”——你怎么知道这是库代码?我似乎错过了问题中的那一部分。代码片段似乎很容易成为某些 UI 代码的一部分,而 ConfigureAwait(false) 是完全错误的做法。 @PeterDuniho 但我们确实看到了第一个 await 和 return 语句之间的所有代码,那里似乎没有 UI 调用,所以这将是 ConfigureAwait(false) 的一个很好的候选者跨度> @ScottChamberlain:可能,但没有上下文很难确定。据我们所知,OP 只是省略了与 UI 相关的内容。我担心的是,将ConfigureAwait(false) 添加到不应该出现的代码中比在应该出现的代码中没有它要糟糕得多。 IE。后者只是一种优化(对于其他正确的代码),而前者会破坏某些东西。 @roryap:ConfigureAwait(false) 告诉任务它确实不需要需要在当前上下文中运行延续。这适用于与 UI 代码无关的任何内容;但是如果延续包含需要在当前上下文(即UI线程)中执行的代码,例如访问一些UI对象,调用ConfigureAwait(false)会导致延续在UI线程以外的线程上运行,结果当访问 UI 对象时,将抛出一个 InvalidOperationException(或类似的......那是 Winforms,我不记得 WPF 的想法)。 @roryap: 使用Invoke() 可以解决问题,但也几乎否定了在这种情况下使用await 的全部意义。【参考方案3】:

这是 ReadAsStringAsync 的 .NET 源代码。 如果您深入了解 LoadIntoBufferAsync() 方法,您会发现这将继续从 HttpResponse 读取缓冲区并导致潜在的进一步网络调用。这意味着使用 await 而不是 Result 是一个好习惯。

[__DynamicallyInvokable]
    public Task<string> ReadAsStringAsync()
    
      this.CheckDisposed();
      TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
      HttpUtilities.ContinueWithStandard(this.LoadIntoBufferAsync(), (Action<Task>) (task =>
      
        if (HttpUtilities.HandleFaultsAndCancelation<string>(task, tcs))
          return;
        if (this.bufferedContent.Length == 0L)
        
          tcs.TrySetResult(string.Empty);
        
        else
        
          Encoding encoding1 = (Encoding) null;
          int index = -1;
          byte[] buffer = this.bufferedContent.GetBuffer();
          int dataLength = (int) this.bufferedContent.Length;
          if (this.Headers.ContentType != null)
          
            if (this.Headers.ContentType.CharSet != null)
            
              try
              
                encoding1 = Encoding.GetEncoding(this.Headers.ContentType.CharSet);
              
              catch (ArgumentException ex)
              
                tcs.TrySetException((Exception) new InvalidOperationException(SR.net_http_content_invalid_charset, (Exception) ex));
                return;
              
            
          
          if (encoding1 == null)
          
            foreach (Encoding encoding2 in HttpContent.EncodingsWithBom)
            
              byte[] preamble = encoding2.GetPreamble();
              if (HttpContent.ByteArrayHasPrefix(buffer, dataLength, preamble))
              
                encoding1 = encoding2;
                index = preamble.Length;
                break;
              
            
          
          Encoding encoding3 = encoding1 ?? HttpContent.DefaultStringEncoding;
          if (index == -1)
          
            byte[] preamble = encoding3.GetPreamble();
            index = !HttpContent.ByteArrayHasPrefix(buffer, dataLength, preamble) ? 0 : preamble.Length;
          
          try
          
            tcs.TrySetResult(encoding3.GetString(buffer, index, dataLength - index));
          
          catch (Exception ex)
          
            tcs.TrySetException(ex);
          
        
      ));
      return tcs.Task;
    

【讨论】:

以上是关于如果我等待我正在执行 ReadAsStringAsync() 的响应,我应该等待 ReadAsStringAsync() 吗?的主要内容,如果未能解决你的问题,请参考以下文章

执行sql脚本,不等待完成

添加到相同的php页面并等待用户输入

如何等待Jenkinsfile“并行”块内的所有执行程序?

创建执行动画的等待队列

在猫鼬中执行 CRUD 操作时异步/等待

如果应用程序正在等待审核,我如何创建临时版本?