Запрос не асинхронный - PullRequest
0 голосов
/ 19 мая 2019
public class RollingRequests
    {
        private const int DefaultNumSimultaneousRequests = 10;
        private readonly HttpClient _client; // Don't worry about disposing see https://stackoverflow.com/questions/15705092/do-httpclient-and-httpclienthandler-have-to-be-disposed
        private readonly HttpCompletionOption _httpCompletionOption;
        private readonly int _numSimultaneousRequests;

        public RollingRequests() : this(DefaultNumSimultaneousRequests)
        {
        }

        public RollingRequests(int windowSize) : this(new HttpClient(), windowSize)
        {
        }

        public RollingRequests(HttpClient client, int numSimultaneousRequests, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead)
        {
            _client = client;
            _numSimultaneousRequests = numSimultaneousRequests;
            _httpCompletionOption = httpCompletionOption;
        }

        public async Task ExecuteAsync(List<string> urls, CancellationToken cancellationToken, Action<HttpResponseHeaders, string> requestCallback = null)
        {
            var nextIndex = 0;
            var activeTasks = new List<Task<Tuple<string, HttpResponseMessage>>>();

            var startingIndex = Math.Min(_numSimultaneousRequests, urls.Count);
            for (nextIndex = 0; nextIndex < startingIndex; nextIndex++)
            {
                activeTasks.Add(RequestUrlAsync(urls[nextIndex], cancellationToken));
            }

            while (activeTasks.Count > 0)
            {
                var finishedTask = await Task.WhenAny(activeTasks).ConfigureAwait(false);
                activeTasks.Remove(finishedTask);

                var retryUrl = await ProcessTask(await finishedTask, requestCallback).ConfigureAwait(false);

                // If retrying, add the URL to the end of the queue
                if (retryUrl != null)
                {
                    urls.Add(retryUrl);
                }

                if (nextIndex < urls.Count)
                {
                    activeTasks.Add(RequestUrlAsync(urls[nextIndex], cancellationToken));
                    nextIndex++;
                }
            }
        }

        private async Task<string> ProcessTask(Tuple<string, HttpResponseMessage> result, Action<HttpResponseHeaders, string> requestCallback = null)
        {
            var url = result.Item1;
            using (var response = result.Item2)
            {
                if (!response.IsSuccessStatusCode)
                {
                    return url;
                }

                if (requestCallback != null)
                {
                    string content = null;
                    if (_httpCompletionOption == HttpCompletionOption.ResponseContentRead)
                    {
                        content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
                    }

                    requestCallback(response.Headers, content);
                }

                return null;
            }
        }

        private async Task<Tuple<string, HttpResponseMessage>> RequestUrlAsync(string url, CancellationToken ct)
        {
            var response = await _client.GetAsync(url, _httpCompletionOption, ct).ConfigureAwait(false);
            return new Tuple<string, HttpResponseMessage>(url, response);
        }
    }

Это класс, который позволяет одновременно выполнять X одновременных запросов.Когда я тестирую этот класс модулем, и я запускаю HttpClient, который дает каждому запросу 1-секундную задержку, первоначальный activeTasks.Add занимает 5 секунд, если у меня есть 5 запросов, предлагая мне, что RequestUrlAsync не является действительно асинхронным.

Может кто-нибудь заметить проблему?

Редактировать: Вот как я сплю осмеянный клиент

        _messageHandlerMock
                .Protected()
                .Setup<Task<HttpResponseMessage>>(MethodToMoq, ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
                .Callback(() => Thread.Sleep(1000))
                .ReturnsAsync(callback)
                .Verifiable();

Ответы [ 2 ]

1 голос
/ 20 мая 2019

Я проверил ваш класс RollingRequests с реальными URL-адресами и работает как ожидалось. Затем я заменил await _client.GetAsync(... на await Task.Delay(1000) и продолжил работать как положено. Затем замените ту же строку на Thread.Sleep(1000) и скопируйте вашу проблему.

Урок морали: избегайте блокировки текущего потока при выполнении асинхронного кода!

0 голосов
/ 19 мая 2019

(Было бы проще ответить, если бы вы предоставили Минимальный воспроизводимый пример )

Смешивание Thread.Sleep с асинхронным кодом не очень хорошая идея, потому что это блокировкаcall.

Следует также избегать насмешливых внутренних операций.

Вот простой пример теста, для выполнения которого требуется около 1 секунды:

async Task Test()
{
    var httpClient = new HttpClient(new TestHttpMessageHandler());

    var ticks = Environment.TickCount;

    await Task.WhenAll(Enumerable.Range(0, 10).Select(_ => httpClient.GetAsync("https://stackoverflow.com/")));

    Console.WriteLine($"{Environment.TickCount - ticks}ms");
}

class TestHttpMessageHandler : HttpMessageHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        await Task.Delay(1000);

        return new HttpResponseMessage();
    }
}
...