如果我等待了正在执行ReadAsStringAsync()的响应,那么是否应该等待ReadAsStringAsync()呢?

34

如果我在等待执行ReadAsStringAsync()的响应时awaited,那么我是否应该再次awaitReadAsStringAsync()?更进一步澄清,以下两种方式之间的区别或正确的方式是什么?它们是否有效地相同?

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

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

1
我认为只有在你想在使用结果之前对任务进行某些操作时,才会执行 var content = response.Content.ReadAsStringAsync(); - rory.ap
5
永远不要执行 Map(content.Result),否则会导致程序死锁,如果你打算延迟执行它,仍需使用 await。应该执行 Map(await content) - Scott Chamberlain
3个回答

27
您的第一个示例是正确的。第二个示例在异步操作期间不会产生收益。相反,通过获取content.Result属性的值,您强制当前线程等待异步操作完成。
此外,正如评论者Scott Chamberlain指出的那样,通过阻止当前线程,您可能会引入死锁的可能性。这取决于上下文,但await的常见情景是在UI线程中使用该语句,而UI线程需要保持响应以满足各种需求,包括能够处理等待操作的完成。
如果您避免第二种模式,即从未知已完成的Task中检索Result属性的值,不仅可以确保高效使用线程,还可以避免这种常见的死锁陷阱。

8
作为上一段的延伸,等待一个已经完成的任务不会产生额外的费用,它只会同步返回已经可用的结果。如果您正在一个已标记为 async 的方法中,则没有理由在任务上调用 .Result 而不是使用 await - Scott Chamberlain
我希望看到@ScottChamberlain在这里给出的其他评论被添加到答案中,因为这可能是OP所需要的最好信息。他们的示例导致了关于使用Result进行阻塞的混淆(因此有了大部分的答案),但他们仍然需要等待已经等待的Response方法。你不必担心等待可等待的对象;最坏的情况是你立即得到了结果,听起来像是一个积极的结果。 - ChiefTwoPencils

14
ReadAsString是一个async方法的原因在于,实际上读取数据是一项IO操作。即使你已经有了http结果,内容可能还没有完全加载完成。没有额外的线程或大量的计算负担。 HttpClient.GetAsync允许您添加HttpCompletionOption,以便GetAsync仅在整个HttpResult已加载完毕后返回。在这种情况下,HttpContent.ReadAsStringAsync将同步完成(所谓的快速路径),因为内容已经存在。
因此,您应该确保使用await等待它完成。
另外:由于这可能是不依赖UI线程返回的库代码,您应该在所有等待的方法调用中添加.ConfigureAwait(false)

2
“由于这是库代码” - 你怎么知道这是库代码?我好像在问题中错过了那部分。这些代码片段似乎也可以很容易地成为某些UI代码的一部分,而在那里使用 ConfigureAwait(false) 将完全是错误的。 - Peter Duniho
1
@PeterDuniho 但是我们可以看到第一个await和返回语句之间的所有代码,似乎没有UI调用,因此这将是使用ConfigureAwait(false)的好选择。 - Scott Chamberlain
3
@ScottChamberlain:可能是这样,但没有上下文很难确定。我们不知道OP是否仅省略了与UI相关的内容。我担心的是,在本应不存在的代码中添加 ConfigureAwait(false) 比在本应存在的代码中没有它要糟糕得多。也就是说,后者只是一种优化(针对其他正确的代码),而前者会破坏某些内容。 - Peter Duniho
4
ConfigureAwait(false) 告诉任务它不需要在当前上下文中运行继续执行。这对于任何与UI代码无关的内容都是可以的;但是,如果继续执行包括需要在当前上下文中执行的代码(例如访问某些UI对象),调用ConfigureAwait(false)将导致继续执行在其他线程上运行,结果当访问UI对象时将抛出一个 InvalidOperationException 异常(或类似异常... 这是针对 Winforms 的,我不能立即回忆起 WPF 的)。 - Peter Duniho
2
@roryap:使用Invoke()可以解决问题,但在这种情况下,基本上会抵消使用await的整个意义。 - Peter Duniho
显示剩余3条评论

8

以下是 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;
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接