Отмените несколько вызовов HttpClient, используя CancellationToken и Ctrl + C - PullRequest
0 голосов
/ 29 мая 2018

Я пытаюсь отменить несколько асинхронных веб-запросов (GET), отправленных через общий HttpClient с использованием CancellationTokens, в приложении командной строки, полный каркас, .net 4.7.1, C # 7.3, VS 2017.

В моем примере выполняется несколько параллельных задач, каждая задача постоянно загружает некоторые данные через Http, используя асинхронные GetAsync() и ReadAsStringAsync(), но я получаю тот же результат, используя, например, ReadAsByteArrayAsync().

Завершение выполняется путем подключения к Console.CancelKeyPress и отмены моего CancellationTokenSource.

Хотя это кажется очевидным по какой-то причине, я не могу обойти это и найти решение, которое даетнадежный результат.Иногда все выключается, как и ожидалось, то есть все задачи завершены (отменены), но чаще всего, что не выключение просто кажется зависает.Запуск без отладчика (с / без DEBUG) завершает работу приложения, но не так, как я ожидал.Меньшее количество задач означает более вероятное чистое завершение работы.

Приостановка всех потоков в отладке в состоянии «зависания», по-видимому, указывает на то, что некоторые потоки застряли в GetAsync(), но довольно сложно точно определить, что именнопродолжается.

На самом деле не так уж важно, как выходит приложение, но я хотел бы понять это и иметь возможность последовательно производить чистое и контролируемое отключение, что, по-моему, возможно при использовании этой конструкции, ноСкорее всего, я пропустил некоторые детали.

using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace HttpClientAsyncTest
{
    class Program
    {
        static async Task<int> Main()
        {
            using (var cancellationTokenSource = new CancellationTokenSource())
            {
                Console.CancelKeyPress += (sender, a) =>
                {
                    Console.WriteLine("Stopped by user");
                    cancellationTokenSource.Cancel();
                };

                var task = RunAsync(cancellationTokenSource);
                Console.WriteLine("Running - Press CTRL+C to stop");
                await task;
            }

            Console.WriteLine("All done, exiting");

            return 0;
        }

        private static async Task RunAsync(CancellationTokenSource cts)
        {
            using (var client = new HttpClient())
            {
                try
                {
                    var tasks = Enumerable.Range(1, 10).Select(id => ProcessBatchAsync(client, id, cts.Token));

                    await Task.WhenAll(tasks);
                }
                catch (OperationCanceledException)
                {
                    Console.WriteLine("Operation cancelled somehow");
                }
            }
        }

        private static async Task ProcessBatchAsync(HttpClient client, int id, CancellationToken cancellationToken)
        {
            while (true)
                await ProcessNextBatchAsync(client, id, cancellationToken);
        }

        private static async Task ProcessNextBatchAsync(HttpClient client, int id, CancellationToken cancellationToken)
        {
            using (var response = await client.GetAsync("http://some.payload.to.request", cancellationToken))
            {
                if (response.StatusCode == HttpStatusCode.NotFound)
                    return;

                var data = await response.Content.ReadAsStringAsync();
                Console.WriteLine($"Id: {id} downloaded {data.Length} chars");
            }
        }
    }
}

Я ожидаю увидеть что-то подобное при нажатии Ctrl-C:

Running - Press CTRL+C to stop
Id: 6 downloaded 475357 chars
Id: 2 downloaded 475141 chars
Id: 3 downloaded 474927 chars
Id: 5 downloaded 474457 chars
Id: 8 downloaded 474524 chars
Id: 4 downloaded 474643 chars
Id: 7 downloaded 475133 chars
Id: 9 downloaded 475316 chars
Stopped by user
Operation cancelled somehow
All done, exiting
Press any key to continue . . .

Но в основном я получаю что-то вроде:

Running - Press CTRL+C to stop
Id: 3 downloaded 474927 chars
Id: 8 downloaded 474524 chars
Id: 5 downloaded 474457 chars
Id: 9 downloaded 475316 chars
Id: 6 downloaded 475357 chars
Id: 7 downloaded 474952 chars
Id: 2 downloaded 475513 chars
Stopped by user
Id: 4 downloaded 475457 chars
Id: 7 downloaded 475133 chars
^CPress any key to continue . . .

1 Ответ

0 голосов
/ 29 мая 2018

Ну, резиновая утка через StackOverflow снова сработала.

Ctrl+C закрывает приложение, если не отменено с помощью args.Cancel = true, поэтому оно имеет абсолютный ноль для HttpClient или CancellationToken.

Исправление просто:

Console.CancelKeyPress += (sender, args) =>
{
    args.Cancel = true;
    Console.WriteLine("Stopped by user");
    cancellationTokenSource.Cancel(true);
};
...