Deadlock reading async response content from async

2019-05-23 16:54发布

问题:

Problem

I'm using a DelegatingHandler in WebAPI to read the response Content and audit it to the database.

When the code gets to .ReadAsStringAsync() it appears to lock and prevent other requests from completing. I only know this, because when I remove the offending line, it works perfectly.

public static void Register(HttpConfiguration config)
{
    config.MessageHandlers.Add(new ResourceAuditHandler());
}

public class ResourceAuditHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
                                                           CancellationToken cancellationToken)
    {
        var response = await base.SendAsync(request, cancellationToken);

        string content = "";
        if (response.Content != null)
        {
            //THIS SEEMS TO CREATE DEADLOCK!
            content = await response.Content.ReadAsStringAsync(); 
        }

        AuditResponseToDatabase(content);

        return response;
    }
}

Possible cause

I've read this can happen when two bits of the application are trying to read the content from same request/response (a DelegatingHandler and Controller/ModelBinder)... Or more specifically when one tries to read the .Result of the async response, but the other instance has already read the Result / Response Stream, which is then null.

My first request, hits a simple controller, but the second request, hits a controller with an attribute that checks the Action parameter "id" to check its not null. I've read that, when both a DelegatingHandler and the controller ModelBinder are trying to read from the Response you get deadlock.

Alternative method (which also fails)

I've also tried using the other approach (as seen in other SO questions) which uses .ReadAsByteArrayAsync() but this still seems to lock. I understood that this method doesn't use the .Result approach and reads it directly from the response stream (somehow).

 var response = await base.SendAsync(request, cancellationToken);

 string content= "";
 if (response.Content != null)
 {
     var bytes = await response.Content.ReadAsByteArrayAsync();
     responseContent = Encoding.UTF8.GetString(bytes);
 }

 AuditResponseToDatabase(content);

Please help!

回答1:

Peter, first of all, you are right about the cause of this deadlock. Try to use async/await all the way down. In other words, do not block on async code. Another thing you should do is using the .ConfigureAwait(false):

    var response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);

    string content = "";
    if (response.Content != null)
    {
        content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); 
    }

More info you can find at this link

If none of the above work, please provide a complete example, ok?

Hope this helps!