HttpContent.ReadAsStringAsync causes request to hang (or other strange behaviours)

alextercete picture alextercete · Jul 22, 2013 · Viewed 13.9k times · Source

We are building a highly concurrent web application, and recently we have started using asynchronous programming extensively (using TPL and async/await).

We have a distributed environment, in which apps communicate with each other through REST APIs (built on top of ASP.NET Web API). In one specific app, we have a DelegatingHandler that after calling base.SendAsync (i.e., after calculating the response) logs the response to a file. We include the response's basic information in the log (status code, headers and content):

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;
}

The problem is this: when the code reaches content.ReadAsStringAsync().Result under heavy server load, the request sometimes hangs on IIS. When it does, it sometimes returns a response -- but hangs on IIS as if it didn't -- or in other times it never returns.

I have also tried reading the content using ReadAsByteArrayAsync and then converting it to String, with no luck.

When I convert the code to use async throughout I get even weirder results:

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();
}

Now HttpContext.Current is null after the call to content.ReadAsStringAsync(), and it keeps being null for all the subsequent requests! I know this sounds unbelievable -- and it took me some time and the presence of three coworkers to accept that this was really happening.

Is this some kind of expected behavior? Am I doing something wrong here?

Answer

victordscott picture victordscott · Dec 4, 2014

I had this problem. Although, I haven't fully tested yet, using CopyToAsync instead of ReadAsStringAsync seems to fix the problem:

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

var sr = new StreamReader(ms);
responseContent = sr.ReadToEnd();