Мой цикл запросов GET с HttpClient попадает в загадочный поток исключений AggregateExceptions

#c# #multithreading #http

#c# #многопоточность #http

Вопрос:

Я новичок в этом языке, новичок в HttpClient и плохо разбираюсь в многопоточности, так что постарайтесь потерпеть меня. У меня есть приложение Windows Forms, которое позволяет пользователю вводить идентификатор. Когда они нажимают «Пуск», он начнет повторно отправлять запросы к веб-API определенного веб-сайта (пока они не нажмут «Стоп»), каждый раз увеличивая идентификатор на 1. Идентификатор включается в запросы; это заставляет сайт возвращать таблицу JSON виртуального объекта, связанного с этим идентификатором. Тысячи этих объектов загружаются на сайт в минуту.

Вот метод, который я использую для выполнения запросов:

 private async Task<string> GetResponseText(HttpClient client, string address)
{
        return await client.GetStringAsync(address);
}
  

Итак, в той части моего кода, где я обрабатываю, что происходит, когда программа собирается начать сканирование, я сначала создаю новый HttpClient, например, так:

 HttpClient client = new HttpClient();
  

Затем я создаю совершенно новый поток и начинаю цикл:

 System.Threading.Thread scanThread = new System.Threading.Thread(() =>
{
    while (scanning)
    {
        string assetUrl = "https://api.example.com/blah?id="   assetId.ToString();
        string response = GetResponseText(client, assetUrl).Resu<
        dynamic assetJson = JsonConvert.DeserializeObject(response);
        if (assetJson != null)
        {
            this.Invoke((MethodInvoker)delegate
            {
                try
                {
                    // omitted lots of irrelevant stuff
                    assetId  = 1;
                }
                catch (RuntimeBinderException)
                {
                    // this probably means the ID doesn't exist yet so I do nothing
                }
            });
        }
    }
    System.Threading.Thread.CurrentThread.Abort();
});
scanThread.IsBackground = true;
scanThread.Start();
  

Когда я запускал это в первый раз, все работало нормально. Это удалось с каждым отдельным идентификатором, и он отправлял не менее 5 или 10 запросов в секунду. Не было никаких странных исключений или проблем за кулисами или чего-то подобного. Но затем, как гром среди ясного неба, после, может быть, сотни или двух запросов, у меня возникла моя первая проблема:

 A first chance exception of type 'System.Net.Http.HttpRequestException' occurred in mscorlib.dll
A first chance exception of type 'System.AggregateException' occurred in mscorlib.dll
An unhandled exception of type 'System.AggregateException' occurred in mscorlib.dll
Additional information: One or more errors occurred.
  

Трассировка стека сузила проблему до этой строки:

 string response = GetResponseText(client, assetUrl).Resu<
  

«Все в порядке», — подумал я про себя. Я не знал, что было не так, поэтому я настроил блок try / catch для этой строки. И это та часть, которая действительно заставляет меня почесать затылок.

С помощью try / catch на месте, как только возникает эта ошибка, она просто продолжает повторяться снова и снова для всего цикла. Это никогда не восстанавливается и не продолжается нормально. И есть два способа исправить это, второй из которых самый странный:

1) Просто закройте программу и откройте ее снова

2) Остановите сканирование, терпеливо подождите, пока в отладочном выводе Visual Studio появится около 6 строк о выходе потоков, затем снова запустите сканирование

Этот второй сценарий, который, как мне кажется, является ключом к пониманию того, почему это происходит, но я недостаточно хорош, чтобы разобраться в этом. Что происходит? И почему ожидание закрытия некоторых потоков (с чего бы они ни начинались) устраняет проблему? Могу ли я просто избавляться от них вручную каждый раз, когда выполняется цикл?

Комментарии:

1. Пожалуйста, попробуйте тот же подход, используя основной поток. Поэтому удалите свой поток и передайте цикл while непосредственно в вашей форме. Теперь в конце вашего цикла добавьте приложение. DoEvents(); Так что вы можете отреагировать и нажать кнопку stop или sth. Это не решение, но если это поможет вам сейчас, то только ваша потоковая обработка вызывает эти проблемы.

2. Углубитесь в AggregateExceptions и узнайте, каковы внутренние исключения

3. @Sebi Application. DoEvents() остановил ответ всего приложения, даже если по какой-то причине оно было помещено внутри цикла while

4. @AntP Я не уверен, как это сделать

5. @Autumn вводите точку останова, когда вы перехватываете исключения и проверяете InnerExceptions свойство.

Ответ №1:

HTTP-Statuscode 400 означает, что ваш запрос недействителен. Поэтому вам нужно подробно проверить свой HTTP-запрос. Возможно, API разрешает только ограниченное количество access или sth.

Далее я приведу вам несколько примеров кода, которые являются более современными:

    var ts = new CancellationTokenSource();
   CancellationToken ct = ts.Token;

    private void Start()
    {
       var task = Task.Factory.StartNew(() => {

         var client = new HttpClient();
         while(true)
         {
            if (ct.IsCancellationRequested)
            {
               break;
            }

            string assetUrl = "https://api.example.com/blah?id="   assetId;
            string response = await GetResponseText(client, assetUrl);
            dynamic assetJson = JsonConvert.DeserializeObject(response);

            if (assetJson != null)
            {
               assetId  ;
            }
         }
       }, ct).ContinueWith(ex => Console.WriteLine(ex.Message), TaskContinuationOptions.OnlyOnFaulted);
     }


     private void Stop()
     {
        ct.Cancel();
     }
  

Комментарии:

1. Спасибо. Я совершенно уверен, что проблема связана с этим конкретным API, и я рассмотрю это в следующий раз, когда смогу. И современный код полезен.

2. @Осень, добро пожаловать. Да, я думаю, что api тоже будет проблемой.