Почему отмена блокируется так долго при отмене большого количества HTTP-запросов?

Фон

У меня есть код, который выполняет пакетную обработку HTML-страниц с использованием контента с одного конкретного хоста. Он пытается сделать большое количество (~ 400) одновременных HTTP-запросов, используяHttpClient, Я считаю, что максимальное количество одновременных подключений ограниченоServicePointManager.DefaultConnectionLimitпоэтому я не применяю свои собственные ограничения параллелизма.

После отправки всех запросов асинхронноHttpClient с помощьюTask.WhenAll, вся пакетная операция может быть отменена с помощьюCancellationTokenSource а такжеCancellationToken, Ход выполнения операции можно просмотреть через пользовательский интерфейс, и можно нажать кнопку, чтобы выполнить отмену.

проблема

Призыв кCancellationTokenSource.Cancel() блоки в течение примерно 5 - 30 секунд. Это приводит к зависанию пользовательского интерфейса. Есть подозрение, что это происходит потому, что метод вызывает код, который зарегистрирован для уведомления об отмене.

Что я учелОграничение количества одновременных задач HTTP-запроса. Я считаю это обходным путем, потому чтоHttpClient кажется, уже ставит в очередь лишние запросы сам.ВыполнениеCancellationTokenSource.Cancel() вызов метода в потоке без пользовательского интерфейса. Это не сработало; задача фактически не выполнялась, пока большинство других не закончилось. Я думаюasync версия метода будет работать хорошо, но я не смог найти. Кроме того, у меня сложилось впечатление, что этот метод целесообразно использовать в потоке пользовательского интерфейса.демонстрацияКод
class Program
{
    private const int desiredNumberOfConnections = 418;

    static void Main(string[] args)
    {
        ManyHttpRequestsTest().Wait();

        Console.WriteLine("Finished.");
        Console.ReadKey();
    }

    private static async Task ManyHttpRequestsTest()
    {
        using (var client = new HttpClient())
        using (var cancellationTokenSource = new CancellationTokenSource())
        {
            var requestsCompleted = 0;

            using (var allRequestsStarted = new CountdownEvent(desiredNumberOfConnections))
            {
                Action reportRequestStarted = () => allRequestsStarted.Signal();
                Action reportRequestCompleted = () => Interlocked.Increment(ref requestsCompleted);
                Func<int, Task> getHttpResponse = index => GetHttpResponse(client, cancellationTokenSource.Token, reportRequestStarted, reportRequestCompleted);
                var httpRequestTasks = Enumerable.Range(0, desiredNumberOfConnections).Select(getHttpResponse);

                Console.WriteLine("HTTP requests batch being initiated");
                var httpRequestsTask = Task.WhenAll(httpRequestTasks);

                Console.WriteLine("Starting {0} requests (simultaneous connection limit of {1})", desiredNumberOfConnections, ServicePointManager.DefaultConnectionLimit);
                allRequestsStarted.Wait();

                Cancel(cancellationTokenSource);
                await WaitForRequestsToFinish(httpRequestsTask);
            }

            Console.WriteLine("{0} HTTP requests were completed", requestsCompleted);
        }
    }

    private static void Cancel(CancellationTokenSource cancellationTokenSource)
    {
        Console.Write("Cancelling...");

        var stopwatch = Stopwatch.StartNew();
        cancellationTokenSource.Cancel();
        stopwatch.Stop();

        Console.WriteLine("took {0} seconds", stopwatch.Elapsed.TotalSeconds);
    }

    private static async Task WaitForRequestsToFinish(Task httpRequestsTask)
    {
        Console.WriteLine("Waiting for HTTP requests to finish");

        try
        {
            await httpRequestsTask;
        }
        catch (OperationCanceledException)
        {
            Console.WriteLine("HTTP requests were cancelled");
        }
    }

    private static async Task GetHttpResponse(HttpClient client, CancellationToken cancellationToken, Action reportStarted, Action reportFinished)
    {
        var getResponse = client.GetAsync("http://www.google.com", cancellationToken);

        reportStarted();
        using (var response = await getResponse)
            response.EnsureSuccessStatusCode();
        reportFinished();
    }
}
Выход

Почему отмена блокируется так долго? Кроме того, есть что-то, что я делаю неправильно или могло бы быть лучше?

Ответы на вопрос(1)

Ваш ответ на вопрос