【发布时间】:2013-07-21 18:11:51
【问题描述】:
我们正在构建一个高度并发的 Web 应用程序,最近我们开始广泛使用异步编程(使用 TPL 和async/await)。
我们有一个分布式环境,其中应用程序通过 REST API(构建在 ASP.NET Web API 之上)相互通信。在一个特定的应用程序中,我们有一个DelegatingHandler,它在调用base.SendAsync 之后(即在计算响应之后)将响应记录到文件中。我们在日志中包含响应的基本信息(状态码、标头和内容):
public static string SerializeResponse(HttpResponseMessage response)
{
var builder = new StringBuilder();
var content = ReadContentAsString(response.Content);
builder.AppendFormat("HTTP/{0} {1:d} {1}", response.Version.ToString(2), response.StatusCode);
builder.AppendLine();
builder.Append(response.Headers);
if (!string.IsNullOrWhiteSpace(content))
{
builder.Append(response.Content.Headers);
builder.AppendLine();
builder.AppendLine(Beautified(content));
}
return builder.ToString();
}
private static string ReadContentAsString(HttpContent content)
{
return content == null ? null : content.ReadAsStringAsync().Result;
}
问题是这样的:当代码到达content.ReadAsStringAsync().Result 在服务器负载很重的情况下,请求有时会在IIS 上挂起。当它返回时,它有时会返回响应——但在 IIS 上挂起,就好像它没有返回一样——或者在其他时候它永远不会返回。
我也尝试使用ReadAsByteArrayAsync 读取内容,然后将其转换为String,但没有成功。
当我将代码转换为始终使用异步时,我会得到更奇怪的结果:
public static async Task<string> SerializeResponseAsync(HttpResponseMessage response)
{
var builder = new StringBuilder();
var content = await ReadContentAsStringAsync(response.Content);
builder.AppendFormat("HTTP/{0} {1:d} {1}", response.Version.ToString(2), response.StatusCode);
builder.AppendLine();
builder.Append(response.Headers);
if (!string.IsNullOrWhiteSpace(content))
{
builder.Append(response.Content.Headers);
builder.AppendLine();
builder.AppendLine(Beautified(content));
}
return builder.ToString();
}
private static Task<string> ReadContentAsStringAsync(HttpContent content)
{
return content == null ? Task.FromResult<string>(null) : content.ReadAsStringAsync();
}
现在HttpContext.Current 在调用content.ReadAsStringAsync() 后为空,对于所有后续请求,它一直为空!我知道这听起来令人难以置信——我花了一些时间和三位同事的在场才接受这真的发生了。
这是某种预期的行为吗?我在这里做错了吗?
【问题讨论】:
-
您确实意识到调用
ReadContentAsStringAsync然后立即调用Result基本上是在否定异步,对吧?这将阻塞,直到作业完成。而HttpContext.Current在等待之后成为null听起来好像它只是没有流过await点,这很烦人,但并没有完全让我感到惊讶。您可以在异步方法的 start 处获取它,然后使用该局部变量... -
在尝试处理内容之前,我可能会在调用 response.EnsureSuccessStatusCode() 之前调用 wrap a try catch。
-
你确定总是可以阅读
HttpResponseMessage.Content吗?在我看来,可能不支持尝试读取您自己的输出流。 -
@JonSkeet,是的,我知道这一点。我“阻止”该操作的唯一原因是试图避免丢失
HttpContext.Current“永远”——我做到了,但随后出现了悬而未决的问题。顺便说一句,我认为您的理解不正确,但HttpContext.Current在 HTTP 请求结束之前不会丢失。后续 HTTP 请求会丢失它。我只能通过iisreset找回它。 -
@StephenCleary,你这是什么意思?就我而言,我不是直接将流写入
response.Content,而是使用ObjectContent。
标签: c# async-await c#-5.0 httpcontent