Почему я не могу использовать HttpClient для синхронных звонков из ASP.Net? - PullRequest
0 голосов
/ 11 сентября 2018

Я использую клиентскую библиотеку для доступа к стороннему API.Библиотека была сгенерирована NSwagStudio из документации Swagger.

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

Когда я тестирую клиентскую библиотеку из модульного теста, она работает нормально.Когда я пытаюсь вызвать его из приложения ASP.Net, я получаю следующую ошибку:

Утилита CancellationTokenSource.

Я перегнал клиентаБиблиотека, чтобы продемонстрировать проблему, я выбрал опцию для предоставления методов синхронизации, а также async:

public class ClientApi
{
    private readonly HttpClient _httpClient;

    public ClientApi(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public string BaseUrl { get; set; }

    public object Get()
    {
        return Task.Run(async () => await GetAsync(CancellationToken.None)).GetAwaiter().GetResult();
    }

    /// <returns>OK</returns>
    /// <param name="cancellationToken">
    ///     A cancellation token that can be used by other objects or threads to receive notice of
    ///     cancellation.
    /// </param>
    public async Task<string> GetAsync(CancellationToken cancellationToken)
    {
        var client_ = _httpClient;
        try
        {
            using (var request_ = new HttpRequestMessage())
            {
                request_.Method = new HttpMethod("GET");
                request_.RequestUri = new System.Uri(BaseUrl, System.UriKind.RelativeOrAbsolute);

                var response_ = await client_.SendAsync(
                    request_, 
                    HttpCompletionOption.ResponseHeadersRead, 
                    cancellationToken
                ).ConfigureAwait(false);

                try
                {
                    // Exception occurs on following line
                    var responseData_ = response_.Content == null 
                        ? null 
                        : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
                    return responseData_;
                }
                finally
                {
                    response_?.Dispose();
                }
            }
        }
        finally { }
    }
}

Вот код, который вызывает его:

    protected void OnClick(object sender, EventArgs e)
    {
        var httpClient = new HttpClient();

        var client = new ClientApi(httpClient)
        {
            BaseUrl = "https://www.google.com"
        };

        var html = client.Get();
    }

КодВызов это просто страница asp.net с кнопкой, и события кнопки запускают тот же код, что и модульный тест, который проходит.

Когда я сравниваю прогоны в отладчике: из модульного теста, response_У объекта .Content нет токена отмены, однако при запуске из asp.net он есть.На самом деле они кажутся почти разными объектами, несмотря на то, что GetType () сообщает о них как о System.Net.Http.StreamContent.От декомпиляции класса это не имеет свойства _cancellationtoken, так откуда же его получает отладчик?

From Asp.Net

From Unit Test

Я предполагаю, что http-запрос к моему веб-приложению asp.net имеет свой собственный токен и источник, который каким-то образом используется HttpClient.Однако клиент ожидает всех асинхронных вызовов, чтобы получить результат синхронно, поэтому я не понимаю, как можно расположить базовый CTS, поскольку мы еще не вернулись из вызова клиентской библиотеки.

МожетКто-нибудь понимает, что происходит, и есть ли решение?

1 Ответ

0 голосов
/ 11 сентября 2018

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

«Асинхронизация полностью» означает, что вы не должны смешивать синхронно и асинхронный код без тщательного рассмотрения последствий. В в частности, обычно плохая идея блокировать асинхронный код, вызывая Task.Wait или Task.Result.

Взято из этого великого гида.

По сути, запустив асинхронную синхронизацию кода, вы всегда будете делать что-то не так.

Но если вам действительно нужно одно решение, начните с того, что оберните ваши одноразовые объекты в операторы, а не утилизируйте их вручную. Вот упрощенные решения вашего класса ClientApi, которые делают то, что вам нужно (но это может привести к тупику). Код в основном такой же, как в этом ответе .

public class ClientApi
{
    public object Get(string url)
    {
        using (var client = new HttpClient())
        {
            var response = client.GetAsync(url).Result;
            if (response.IsSuccessStatusCode)
            {
                var responseContent = response.Content;
                return responseContent.ReadAsStringAsync().Result;
            }
        }

    }
}

Подробнее о тупике здесь

...