DownloadFileAsync выдает исключение Get TimeOut - PullRequest
0 голосов
/ 29 декабря 2018

Я поместил Flurl в высокую нагрузку, используя метод DownloadFileAsync для загрузки файлов в частной сети с одного сервера на другой, и через несколько часов метод начинает выдавать исключения "Get TimeOut".Единственное решение, которое нужно решить, это перезапустить приложение.

downloadUrl.DownloadFileAsync(Helper.CreateTempFolder()).Result;

Я добавил второй метод для восстановления после отказа, используя HTTPClient, и его файлы загрузки нормально работают после сбоя flurl, так что это не проблема сервера.

private void DownloadFile(string fileUri, string locationToStoreTo)
       {
           using (var client = new HttpClient())
           using (var response = client.GetAsync(new Uri(fileUri)).Result)
           {
               response.EnsureSuccessStatusCode();

               var stream = response.Content.ReadAsStreamAsync().Result;

               using (var fileStream = File.Create(locationToStoreTo))
               {
                   stream.CopyTo(fileStream);
               }
           }
       }

Есть ли у вас идеи, почему ошибка Get TimeOut запускается всплывающим при высокой нагрузке с использованием метода?

public static Task<string> DownloadFileAsync(this string url, string localFolderPath, string localFileName = null, int bufferSize = 4096, CancellationToken cancellationToken = default(CancellationToken));

Два кода загрузки отличаются только тем, что Flurl повторно использовать HttpClientэкземпляр для всех запросов и мой код уничтожить и создать новый объект HttpClient для каждого нового запроса.Я знаю, что создание и уничтожение HttpClient требует много времени и ресурсов, и я бы предпочел использовать Flurl, если это сработает.

Ответы [ 2 ]

0 голосов
/ 30 декабря 2018

Как отмечают другие, вы пытаетесь использовать Flurl синхронно, вызывая .Result.Это не поддерживается, и при высокой одновременной рабочей нагрузке вы, скорее всего, становитесь свидетелями взаимоблокировок.

Решение HttpClient использует новый экземпляр для каждого вызова, а поскольку экземпляры вообще не используются совместно, вероятно, их меньшесклонны к тупикам.Но это порождает совершенно новую проблему: исчерпание порта .

Короче говоря, если вы хотите продолжить использовать Flurl, продолжайте в том же духе, особенно если вы стали умнее HttpClientповторно использовать «бесплатно».Просто используйте его асинхронно (с async / await), как и предполагалось.См. документы для получения дополнительной информации и примеров.

0 голосов
/ 29 декабря 2018

Я могу вспомнить две или три возможности (я уверен, что есть и другие, о которых я тоже не могу думать)

  1. IP-адрес сервера изменился.

Вы писали, что Flurl повторно использует HttpClient.Я никогда не использовал и даже не слышал о Flurl, поэтому понятия не имею, как это работает.Но HttpClient повторно использует пул соединений, поэтому эффективно повторно использовать один экземпляр и почему это важно делать в приложениях с большим объемом микросервисов, иначе вы, вероятно, исчерпаете все порты, но это даетдругое сообщение об ошибке, а не тайм-аут, так что я знаю, что вы не попали в этот случай.Однако, хотя в краткосрочной перспективе важно повторно использовать HttpClient, HttpClient будет кэшировать результаты DNS, что означает, что важно периодически удалять и создавать новые HttpClient.В недолговечных процессах вы можете использовать статический или одноэлементный экземпляр.Но в длительных процессах вы должны периодически создавать новый экземпляр.Если вы используете его только для доступа к одному серверу, DNS TTL этого сервера является хорошим значением для использования.

Итак, что может произойти, если сервер изменил IP-адреса через несколько часов после запуска вашей программы, и потому что Flurlпродолжайте использовать тот же HttpClient, он не получает новый IP-адрес из записи DNS.Один из способов проверить, является ли это проблемой, - записать IP-адрес сервера в журнал в начале процесса, а при возникновении проблемы проверить, является ли IP-адрес одинаковым или нет.

Если этопроблема, вы можете заглянуть в HttpClientFactory в ASP.NET Core 2.1.Это немного неудобно для использования вне ASP.NET, но я сделал это однажды.Это дает вам возможность повторно использовать HttpClients, чтобы избежать проблемы исчерпания порта TCP при использовании более 32k HttpClients за 120 секунд, а также избежать проблем с кэшированием DNS.Я помню, что по умолчанию он создает новый HttpClient каждые 5 минут.

Достижение максимального количества соединений на сервер

ServicepointManager.DefaultConnectionLimit устанавливает максимальное количество HTTP-соединений, которые клиент открывает для сервера.Если ваш код пытается использовать больше, чем это одновременно, запросы, превышающие лимит, будут ждать, пока существующий HTTP-клиент завершит свой запрос, затем он будет использовать вновь доступное соединение.Однако в прошлом, когда я смотрел на это, тайм-аут HTTP начинался с момента вызова метода HttpClient, а не с того момента, когда HttpClient отправляет запрос на сервер через соединение.Это означает, что если ваш лимит равен 2 и оба используются дольше, чем период ожидания (например, при загрузке 2 больших файлов), для других запросов на загрузку с того же сервера истечет время ожидания, даже если ни один запрос http никогда не отправлялся насервер.

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

Исчерпание пула потоков

Асинхронный код очень эффективен при правильном использовании в высокопараллельных рабочих нагрузках, связанных с вводом-выводом.Иногда я думаю, что это плохая идея - использовать ее где-то еще, потому что это огромный потенциал, вызывающий странные проблемы при неправильном использовании.Как писал Crowcoder в комментарии к вопросу, вы не должны использовать .Result или какой-либо код, блокирующий работающий поток, в асинхронном контексте.Хотя приведенный вами пример кода говорит public void DownloadFile(..., если он на самом деле public async Task DownloadFile(... или если DownloadFile вызывается из асинхронного метода, то существует реальный риск возникновения проблем.Если DownloadFile не вызывается из асинхронного метода, а вызывается в пуле потоков, существует тот же риск ошибок.

Понимание асинхронности - огромная тема, к сожалению, с большим количеством дезинформации в Интернете, поэтому я не могу описать это подробно здесь.Ключевым моментом является то, что асинхронные задачи выполняются в пуле потоков.Итак, если вы вызываете ThreadPool.QueueUserWorkItem и блокируете поток, в котором работает ваш код, или если у вас есть асинхронные задачи, которые вы блокируете (например, путем вызова .Result), то может произойти то, что вы заблокируете каждый поток в пуле потокови когда HTTP-ответ возвращается из сети, во время выполнения .NET нет доступных потоков для выполнения задачи.Проблема с этой идеей в том, что нет также потоков, доступных для сигнализации о тайм-ауте, поэтому я не верю, что вы исчерпываете пул потоков (если бы вы были, я бы ожидал тупиковую ситуацию), но я не знаю, кактаймауты реализованы.Если таймауты / таймеры используют выделенный поток, то может быть возможно, что токен отмены (то, что сигнализирует тайм-аут) может быть установлен потоком таймера, и тогда любой код в блокирующем ожидании либо ответа HTTP, либо токена отмены можетбыть вызванным.Но исчерпание пула потоков обычно вызывает взаимные блокировки, поэтому, если вы получаете сообщение об ошибке, это, вероятно, не так.

Чтобы проверить, не возникают ли проблемы с исчерпанием пула потоков, когда ваша программа начинает получать ошибки тайм-аута, получитедамп памяти вашего приложения (например, с помощью диспетчера задач).Если у вас есть Enterprise или Ultimate SKU Visual Studio, вы можете открыть / отладить дамп памяти в VS.В противном случае вам нужно научиться использовать windbg (или найти другой инструмент).При отладке дампа памяти проверьте количество потоков.Если потоков очень много, это подсказка, что вы можете быть на правильном пути.Проверьте, где был поток во время дампа памяти.Если все они блокируют вызовы, такие как WaitForObject или что-то подобное, существует реальный риск, что вы исчерпали пул потоков.Раньше я никогда не отлаживал проблему тупиковой ситуации / исчерпания пула потоков асинхронных задач, поэтому я не уверен, есть ли способ получить список задач и посмотреть из их среды выполнения, могут ли они быть заблокированы или нет.Если вы когда-либо видите в рабочем состоянии больше задач, чем ядер на вашем ЦП, вы почти наверняка блокируете асинхронную задачу.

Таким образом, вы не предоставили нам достаточно информации, чтобы дать вамответ, который будет работать со 100% уверенностью.Вам нужно продолжать расследование, чтобы понять проблему, пока вы сами не решите ее или не предоставите нам дополнительную информацию.Я дал вам некоторые из наиболее вероятных причин, но это вполне может быть что-то совершенно другое.

...