HttpContent.ReadAsStringAsync导致请求挂起(或其他奇怪的行为)

我们正在构建一个高度并发的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 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 ReadContentAsStringAsync(HttpContent content) { return content == null ? Task.FromResult(null) : content.ReadAsStringAsync(); } 

现在,在调用content.ReadAsStringAsync()之后, HttpContext.Current为null,并且对于所有后续请求它保持为null ! 我知道这听起来令人难以置信 – 我花了一些时间和三个同事的存在才接受这种情况真的发生了。

这是某种预期的行为吗? 我在这里做错了吗?

我有这个问题。 虽然,我尚未完全测试,使用CopyToAsync而不是ReadAsStringAsync似乎解决了这个问题:

 var ms = new MemoryStream(); await response.Content.CopyToAsync(ms); ms.Seek(0, SeekOrigin.Begin); var sr = new StreamReader(ms); responseContent = sr.ReadToEnd(); 

关于你的第二个问题,async / await是编译器构建状态机的语法糖,其中对“await”前面的函数的调用立即在当前线程上返回…其中包含HttpContext.Current的一个线程本地存储。 异步调用的完成可以发生在另一个线程上……一个在其线程本地存储中没有HttpContext.Current的线程。

如果您希望完成在同一个线程上执行(因此在线程本地存储中具有相同的对象,如HttpContext.Current),那么您需要了解此行为。 这对于来自主UI线程(如果您正在构建Windows应用程序)或ASP.NET中的调用尤其重要,来自ASP.NET请求线程的调用,您在此处依赖于HttpContext.Current。

请参阅ConfigureAwait上的参考文档(false)。 另外,查看有关TPL的一些Channel 9教程。 一旦“简单”的东西被弄清楚,主持人就会不可避免地谈论这个问题,因为它会导致一些不容易理解的细微问题,除非你知道TPL在封面下做了什么。

祝好运。

关于您的第一个问题,如果调用者得到结果,我不相信IIS没有完成请求。 您是如何确定此调用方启动的ASP.NET请求线程在IIS中挂起的?