Почему отмена блокируется так долго при отмене большого количества 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 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)

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