如果我等待执行 ReadAsStringAsync() 的响应,我应该等待 ReadAsStringAsync() 吗?
Should I await ReadAsStringAsync() if I awaited the response that I'm performing ReadAsStringAsync() on?
如果我等待我正在执行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);
之所以ReadAsString
是一个async
方法是因为实际读取数据是一个IO操作。即使您已经有了 http 结果,内容也可能没有完全加载。不涉及额外的线程或大量计算。
HttpClient.GetAsync
允许您添加 HttpCompletionOption
以便在整个 HttpResult
加载后仅 GetAsync
return。在那种情况下,HttpContent.ReadAsStringAsync
将同步完成(所谓的快速路径),因为内容已经存在。
所以你一定要等一下。
此外:由于这可能是不依赖于 UI 线程中的 returning 的库代码,因此您应该将 .ConfigureAwait(false)
添加到所有等待的方法调用中。
你的第一个例子是正确的。第二个示例在异步操作期间不产生。相反,通过获取 content.Result
属性 的值,您强制当前线程等待直到异步操作完成。
此外,正如评论者 Scott Chamberlain 指出的那样,通过阻塞当前线程,您可能会引入死锁的可能性。这取决于上下文,但是 await
的常见场景是在 UI 线程中使用该语句,并且 UI 线程需要保持对各种需求的响应,但包括能够实际处理等待操作的完成。
如果避免第二种模式,即从一个你不知道已经完成的Task
中获取Result
属性的值,不仅可以保证高效使用您的线程,您还可以确保避免这种常见的死锁陷阱。
这是 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()
吗?为了进一步澄清,以下内容之间的区别或正确方法是什么?它们实际上是一样的吗?
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);
之所以ReadAsString
是一个async
方法是因为实际读取数据是一个IO操作。即使您已经有了 http 结果,内容也可能没有完全加载。不涉及额外的线程或大量计算。
HttpClient.GetAsync
允许您添加 HttpCompletionOption
以便在整个 HttpResult
加载后仅 GetAsync
return。在那种情况下,HttpContent.ReadAsStringAsync
将同步完成(所谓的快速路径),因为内容已经存在。
所以你一定要等一下。
此外:由于这可能是不依赖于 UI 线程中的 returning 的库代码,因此您应该将 .ConfigureAwait(false)
添加到所有等待的方法调用中。
你的第一个例子是正确的。第二个示例在异步操作期间不产生。相反,通过获取 content.Result
属性 的值,您强制当前线程等待直到异步操作完成。
此外,正如评论者 Scott Chamberlain 指出的那样,通过阻塞当前线程,您可能会引入死锁的可能性。这取决于上下文,但是 await
的常见场景是在 UI 线程中使用该语句,并且 UI 线程需要保持对各种需求的响应,但包括能够实际处理等待操作的完成。
如果避免第二种模式,即从一个你不知道已经完成的Task
中获取Result
属性的值,不仅可以保证高效使用您的线程,您还可以确保避免这种常见的死锁陷阱。
这是 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;
}