请求不异步

时间:2019-05-19 18:15:51

标签: c# asynchronous async-await

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延迟的最小数量是HttpClient,如果我有5个请求,则初始activeTasks.Add将花费5秒,这向我暗示RequestUrlAsync不是真正的异步。

有人可以发现问题吗?

编辑: 这就是我嘲笑的客户端的睡眠方式

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

2 个答案:

答案 0 :(得分:0)

(如果提供了Minimal, Reproducible Example,回答起来会更容易)

Thread.Sleep与异步代码混合并不是一个好主意,因为这是一个阻塞调用。

还应避免嘲弄内部。

下面是一个简单的测试示例,它需要大约1秒钟来执行10个请求:

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

答案 1 :(得分:0)

我用实际的网址测试了您的课程RollingRequests,并按预期工作。然后,我将await _client.GetAsync(...替换为await Task.Delay(1000),然后按预期继续工作。然后用Thread.Sleep(1000)替换同一行,并复制您的问题。

道德课:避免在运行异步代码时阻塞当前线程!