Тупик при чтении содержимого асинхронного ответа из асинхронного DelegatingHandler в webapi

Проблема

Я использую DelegatingHandler в WebAPI для чтения содержимого ответа и проверки его в базе данных.

Когда код попадает в .ReadAsStringAsync(), он блокируется и предотвращает выполнение других запросов. Я знаю только это, потому что, когда я удаляю оскорбительную строку, она работает отлично.

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

Возможная причина

Я читал, что это может произойти, когда два бита приложения пытаются прочитать содержимое одного и того же запроса/ответа (обработчик делегирования и контроллер/связывание модели)... Или, точнее, когда кто-то пытается прочитать .Result< /strong> асинхронного ответа, но другой экземпляр уже прочитал поток результатов/ответов, который затем равен нулю.

Мой первый запрос обращается к простому контроллеру, но второй запрос обращается к контроллеру с атрибутом, который проверяет параметр действия «id», чтобы убедиться, что он не равен нулю. Я читал, что когда и DelegatingHandler, и контроллер ModelBinder пытаются прочитать ответ, вы получаете тупик.

Альтернативный метод (который также не работает)

Я также пытался использовать другой подход (как видно из других вопросов SO), который использует .ReadAsByteArrayAsync(), но он все еще блокируется. Я понял, что этот метод не использует подход .Result и читает его непосредственно из потока ответов (каким-то образом).

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

Пожалуйста помоги!


person peter.swallow    schedule 20.06.2014    source источник
comment
Обычно это происходит, если в вашем коде есть task.Wait() или task.Result (во внешнем стеке вызовов). Так ли это?   -  person noseratio    schedule 20.06.2014
comment
@peter.swallow: Можете ли вы создать небольшой пример, достоверно воспроизводящий проблему?   -  person Stephen Cleary    schedule 20.06.2014


Ответы (1)


Петр, во-первых, ты прав насчет причины этого тупика. Попробуйте использовать async/await до упора. Другими словами, не блокируйте код async. Еще одна вещь, которую вы должны сделать, это использовать .ConfigureAwait(false):

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

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

Дополнительную информацию можно найти по этой ссылке.

Если ничего из вышеперечисленного не работает, пожалуйста, предоставьте полный пример, хорошо?

Надеюсь это поможет!

person Andreo Romera    schedule 20.06.2014
comment
Если он не обращается к Result или не вызывает Wait(), это не поможет. Более того, ConfigureAwait(false) имеет другое значение в ASP.NET, чем в UI или консольных приложениях. stackoverflow.com/questions/13489065/ - person Yuval Itzchakov; 20.06.2014
comment
Это действительно решило мою проблему. К сожалению, это выявило вторую проблему, заключающуюся в том, что при использовании .ConfigureAwait(false) теряется контекст (в данном случае HttpContext), который требуется моему IOC (Autofac). Есть ли другой способ сохранить контекст? - person peter.swallow; 23.06.2014
comment
Вы можете выполнить поиск по теме SynchronizationContext... это может (или не) помочь - person Andreo Romera; 24.06.2014