Warum blockiert die Stornierung so lange, wenn viele HTTP-Anfragen storniert werden?

Hintergrund

Ich habe einen Code, der eine Stapelverarbeitung von HTML-Seiten mit Inhalten von einem bestimmten Host durchführt. Es wird versucht, mithilfe von eine große Anzahl (~ 400) gleichzeitiger HTTP-Anforderungen zu erstellenHttpClient. Ich glaube, dass die maximale Anzahl gleichzeitiger Verbindungen durch begrenzt istServicePointManager.DefaultConnectionLimitDaher wende ich keine eigenen Nebenläufigkeitsbeschränkungen an.

Nach dem Senden aller Anfragen asynchron anHttpClient mitTask.WhenAllkann der gesamte Batch-Vorgang mit abgebrochen werdenCancellationTokenSource undCancellationToken. Der Fortschritt des Vorgangs kann über eine Benutzeroberfläche angezeigt werden, und auf eine Schaltfläche kann geklickt werden, um den Vorgang abzubrechen.

Problem

Der Anruf nachCancellationTokenSource.Cancel() blockiert für ca. 5 - 30 Sekunden. Dadurch friert die Benutzeroberfläche ein. Ist der Verdacht, dass dies auftritt, weil die Methode den Code aufruft, der für die Stornierungsbenachrichtigung registriert wurde.

Was ich in Betracht gezogen habeBegrenzen der Anzahl gleichzeitiger HTTP-Anforderungstasks. Ich halte das für eine Abhilfe, weilHttpClient scheint sich schon überschüssige Anfragen in die Warteschlange zu stellen.Durchführen derCancellationTokenSource.Cancel() Methodenaufruf in einem Nicht-UI-Thread. Das hat nicht so gut funktioniert; Die Aufgabe lief nicht wirklich, bis die meisten anderen fertig waren. Ich denke einasync Die Version der Methode würde gut funktionieren, aber ich konnte keine finden. Außerdem habe ich den Eindruck, dass es geeignet ist, die Methode in einem UI-Thread zu verwenden.DemonstrationCode
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();
    }
}
Ausgabe

Warum blockiert die Stornierung so lange? Gibt es auch etwas, das ich falsch mache oder das ich besser machen könnte?

Antworten auf die Frage(1)

Ihre Antwort auf die Frage