0

Программа выполняет множество HTTP-запросов через restsharp в цикле. На старте объявляется клиент (объявляется 1 раз, затем используется):

IRestClient client = new RestClient();
IRestRequest request = new RestRequest();

В методах соответственно:

request.Resource = "url";
IRestResponse content = client.Get(request);

И с каждым таким запросом использование оперативной памяти увеличивается ровно на размер страницы. Память очищается с помощью GC только при выходе из цикла.

UPD: Больше кода с моими комментариями:

 request.Resource = "url";
 IRestResponse content = client.Get(request); // делаем запрос

Далее регекс:

 Regex stranica = new Regex(@"(регекс)");
 Match stranica1 = stranica.Match(content.Content); // выдергиваем из страницы ее часть (от-до)

Далее ищем в том, что "выдернули" слова:

 Regex r = new Regex(string.Join("|", regex), RegexOptions.IgnoreCase);
 Match m = r.Match(stranica1.Value);

Если нашли одно из слов делаем действие все на том же сайте:

Regex hash = new Regex(@"(регекс)");
Match hash2 = hash.Match(content.Content);
request.Resource = "url";
request.AddParameter("par1", hash2.Value, ParameterType.QueryString);                    
client.Post(request);
check_task(url, id_task); // идем к методу который проверяет то, что сделали (там уже никакой памяти программа не занимает фактически)

По графику использования оперативки вижу, что память использует самый первый блок кода (где IRestResponse content = client.Get(request); // делаем запрос) и в последнем блоке. Адрес запроса там одинаковый. И вот этот, казалось бы простой, код выполняется в 10-100 асинхронных потоков и со временем забивает всю ОЗУ)

UPD2: такая вот картинка в профайлере. РестРеспонс и так все ясно, а стринг - как я понимаю - // выдергиваем из страницы ее часть (от-до) из кода выше. Скриншот для наглядности и время работы тут - 10 минут. Начинали с 30 мб, через час тут будет 1 ГБ, а через 10 часов - 10 гб. И элементы нагружающие память останутся такими же.

введите сюда описание изображения

TonyPak
  • 15
  • 4
  • Предположу, что IRestResponse content как минимум Disposable. – EvgeniyZ Apr 15 '21 at 16:45
  • @EvgeniyZ можно немного более подробно, если не сложно с наброском примера) Я тоже читал про Disposable, но не совсем понял как применить. – TonyPak Apr 15 '21 at 16:47
  • Пытался сделать через using на что получил: IRestResponse тип, использованный в операторе using, должен иметь возможность неявного преобразования в iDisposable – TonyPak Apr 15 '21 at 16:49
  • 1
    А ну попробуйте заменить IRestResponse content на using var content. С кодом я вам не помогу, ибо не очень люблю эту либу, да и вообще не особо вижу в ней смысла, из-за чего ставить не особо охото) А "подробней". disposable объекты надо оборачивать в using, либо вызывать .Dispose() метод, чтоб они успешно закрылись и подчистили за собой. Запросы обычно являются disposable, как в либо - без понятия. – EvgeniyZ Apr 15 '21 at 16:54
  • @EvgeniyZ с var тоже самое, пробовал) – TonyPak Apr 15 '21 at 16:56
  • 1
    Это все не говорит об утечке памяти. Добавьте в цикл GC.Collect() и погялдите. Хотя в рельности, чтобы говорить об утечке, надо смотреть на программу с профайлером памяти. – tym32167 Apr 15 '21 at 17:00
  • @tym32167 Я смотрел с профайлером, могу приложить результаты, но я вас уверяю - все, что вы там увидите - IRestResponse) – TonyPak Apr 15 '21 at 17:02
  • 1
    Поподробнее, пожалуйста. Приложение какого типа, где и как хостится? .NET FW или .NET Core? Какой GC: server или workstation? Процессор занят полностью 100%? Создаются объекты большого размера - нагрузка на LOH? – Alexander Petrov Apr 15 '21 at 17:20
  • @AlexanderPetrov дополнил пост. – TonyPak Apr 15 '21 at 17:20
  • @TonyPak Ну покажите ещё как цикл ваш выглядит что ли. Хотя бы в общих чертах. Что-то внутри цикла не даёт память освободить, наверное. Но без кода сложно понять. – CrazyElf Apr 15 '21 at 17:30
  • @CrazyElf дополнил пост, там сокращенная версия цикла) – TonyPak Apr 15 '21 at 17:31
  • наличие IRestResponse в памяти не означает утечку. Память чистится лениво, а не немедленно. Потому вы можете видеть, что память растет, но вам надо беспокоиться только если она растет и не уменьшается даже после работы GC. – tym32167 Apr 15 '21 at 17:31
  • 1
    100 мегабайт это по сути оч мало, вероятно GC просто не парится на такое количество съеденной памяти. – tym32167 Apr 15 '21 at 17:32
  • @tym32167 в конечном итоге через несколько часов вылетает аутофмемори) Кстати после отметки о сборе мусора в отладчике - тоже ничего не меняется) – TonyPak Apr 15 '21 at 17:33
  • @tym32167это я сделал сейчас скриншот для наглядности, через час там будет не 100 мб, а 1гб, а через 10 часов - 10 гб. – TonyPak Apr 15 '21 at 17:33
  • 1
    Значит кода, что вы показали, недостаточно. Кто держит ссылки на RestResponse? Какой класс? Это можно поглядеть в профайлере памяти, где то в снепшотах. + Надо бы показать больше кода, а то по тем отрывкам, что вы показали, не ясно ничего. – tym32167 Apr 15 '21 at 17:35
  • @tym32167 я немного его сократил, убрав множество request.AddParameter и т.д., но по факту я уже второй день мучаюсь именно с этим кодом. До этого идет условная авторизация на сайте и далее используется только этот метод и метод получения ссылок для работы этого метода (но он точно отношения не имеет). – TonyPak Apr 15 '21 at 17:37
  • @TonyPak Что-то я всё-равно в упор не вижу - цикл то где? ) Покажите, как делается цикл собственно – CrazyElf Apr 15 '21 at 17:39
  • @CrazyElf я это условно назвал циклом. Вызвали метод 1, получили ссылку, вызвали метод 2, отправили 2 пост запроса на полученную ссылку, проверили и опять к методу 1) – TonyPak Apr 15 '21 at 17:44
  • @TonyPak Не понял, у вас методы по кругу вызываются, рекурсивно?! Тогда не удивительно, что ресурсы не освобождаются. Потрудитесь всё же привести схему как это всё у вас устроено хотя бы в общих чертах. Иначе мы все тут просто так время тратим. – CrazyElf Apr 15 '21 at 17:46
  • @CrazyElf да, рекурсивно. Я немного не опытен в этом деле, у меня возникала идея, что дело именно в этом) Как быть?) – TonyPak Apr 15 '21 at 17:47
  • @TonyPak Использовать цикл, очередь, что угодно, но только не рекурсию. Пока вы не вышли из метода, переменные метода держат ссылки на объекты и не дают GC их чистить. При рекурсии вся цепочка вызванных методов держит все использованные объекты! – CrazyElf Apr 15 '21 at 17:50
  • @tym32167 да собственно мы с CrazyElf выше выясняли, что методы у меня вызываются рекурсивно, видимо проблема в этом. Просто на момент написания программы для меня это был самый простой путь и сейчас обсуждается возможно но ли это оставить как есть (так как само по себе оно отлично работает, если бы не память) и решить проблему с памятью или же придется все переделывать. – TonyPak Apr 15 '21 at 17:51
  • 1
    вы пишете о циклах и методах, которых у вас в вопросе нет. Не заставляйте нас гадать, приведите код. – tym32167 Apr 15 '21 at 17:52
  • 1
    @tym32167 да собственно уже выяснено, что у меня рекурсия методов идет и память просто не может очищаться) Спасибо в любом случае всем, кто откликнулся, буду переделывать) – TonyPak Apr 15 '21 at 17:56
  • 1
    А ведь достаточно было нормально показать тело метода, чтобы не размазывать дискуссию на несколько часов. – aepot Apr 15 '21 at 18:02
  • @aepot я извиняюсь за свою неграмотность) Я не думал, что проблема в рекурсии. Точнее я так думал, но потом почему-то откинул эту идею и начал долбится в рестреспонс) – TonyPak Apr 15 '21 at 18:03
  • @TonyPak Вы можете попробовать и внутри рекурсии что-то сделать (самому обнулять ссылки на не нужные уже объекты и т.д.), но я вам настоятельно советую перестроить модель работы и больше никогда не использовать рекурсию без крайней нужды. Без рекурсии можно обойтись практически всегда и это правильно с точки зрения освобождения ресурсов и вообще. – CrazyElf Apr 15 '21 at 18:04
  • Мои советы про нормальный парсер типа HtmlAgiltyPack и HttpClient с асинхронностью в любом случае актуальны. – aepot Apr 15 '21 at 18:05
  • @CrazyElf на момент, когда все это делалось, мне казалось, что так будет на много проще. Зачем передавать условные сотни параметров между методами, если можно объявить их один раз и дальше юзать "метод в методе"... Ну мне так казалось, и все было отлично до этого момента) – TonyPak Apr 15 '21 at 18:06
  • 1
    @TonyPak Во-первых обычно такого не бывает, чтобы нужно было передавать сотни параметров в методы, это что-то странное. Во-вторых обычно в таких случаях делают класс (ну или структуру), где будут лежать все нужные данные, и передают этот класс, а не тонны отдельных параметров. Ну или несколько классов. – CrazyElf Apr 15 '21 at 18:08
  • Как распарсить HTML в .NET? - вот, набирайтесь опыта. – aepot Apr 15 '21 at 18:10
  • @aepot спасибо. – TonyPak Apr 15 '21 at 18:11
  • Этот сайт работает так: кто то задает вопрос, кто то отвечает. Задающий получает решение проблемы, отвечающий получает баллы за ответ, будущие читатели получают базу вопросов/ответов и при похоэей проблеме не задают вопросы снова. На ваш вопрос дать ответ неворзможно, так как вы не показали ничего полезного. Таким образом, вы, вероятно, получили то, зачем пришли, но ни остальные 5 человек, что вам помогали, ни будущие читалели вопроса не получат ничего. – tym32167 Apr 15 '21 at 18:13
  • @tym32167 я уже выше извинился за свою неграмотность, еще раз приношу свои извинения. Проблема оказалась не там, где я ее искал изначально. – TonyPak Apr 15 '21 at 18:14
  • я вас не виню ни в коей мере, просто пояснял как тут что работает, чтобы вы в будущих вопросах это имели ввиду. – tym32167 Apr 15 '21 at 18:16

1 Answers1

1

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

Автору было рекомендовано избавиться от рекурсии. Обычно без неё можно обойтись практически всегда. Можно, например, использовать очередь, в которой хранить задания на обработку и разгребать эту очередь, вызывая (асинхронно) некую функцию для обработки конкретного задания. После окончания обработки задания функция заканчивает свою работу, автоматически освобождая использованные ресурсы. Проблем с памятью больше не должно быть.

CrazyElf
  • 71,194
  • А вы мне не сможете помочь разобраться с этим вопросом? А то потратил 3 часа времени, но даже не понял что мне гуглить) – TonyPak Apr 15 '21 at 22:07
  • @TonyPak Вы так и не показали, как именно вы вызываете функции, как выглядит ваш "цикл" ) А вообще можете начать отсюда https://docs.microsoft.com/ru-ru/dotnet/standard/parallel-programming/task-based-asynchronous-programming – CrazyElf Apr 16 '21 at 06:28