Запуск консольного приложения C# .NET с использованием HtmlAgilityPack, который очищает веб-страницу и выполняет циклы, приводит к утечке памяти. В чем здесь проблема?

Когда я запускаю этот код консольного приложения C# (с использованием проекта с открытым исходным кодом HtmlAgilityPack), использование памяти со временем начинает накапливаться, что выглядит как утечка памяти, но я не могу отследить ее, несмотря на принудительную сборку мусора, повторное использование объекты, присваивая значениям null и очищая информацию.

Где здесь ошибка в моем коде? Или это проблема с HtmlAgilityPack?

После 10 минут работы этого цикла все просто останавливается. И это на системе со 128 ГБ ОЗУ.

using HtmlAgilityPack;


internal class Program
{
    private static int setNum = 16020;
    static HtmlDocument doc = null;
    static HtmlWeb web = new HtmlWeb();

    static void Main(string[] args)
    {
        ScrapeTypeYearName(setNum);

        Console.WriteLine("Press Enter to exit...");
        Console.ReadLine(); // Wait for the user to press Enter
    }

    public static void ScrapeTypeYearName(int setNum)
    {
        string setUrl = "https://www.tcdb.com/Checklist.cfm/sid/" + setNum;

        doc = web.Load(setUrl);

        // Process breadcrumb information
        string category = "";
        string year = "";
        string company = "";
        string setName = "";
        var breadcrumb = doc.DocumentNode.SelectSingleNode("//div[@class='d-none d-md-block']/nav[@aria-label='breadcrumb']");
        if (breadcrumb != null)
        {
            var breadcrumbItems = breadcrumb.SelectNodes("./ol/li/a");
            if (breadcrumbItems != null && breadcrumbItems.Count >= 4)
            {
                category = breadcrumbItems[1].InnerText.Trim();
                year = breadcrumbItems[3].InnerText.Trim();
                Console.WriteLine("Category: " + category);
                Console.WriteLine("Year: " + year);

                var companyNode = breadcrumb.SelectSingleNode("following::h1[@class='site']");
                var setNode = breadcrumb.SelectSingleNode("following::h3[@class='site']");
                if (companyNode != null)
                {
                    company = companyNode.InnerText.Trim();
                    company = company.Replace(year, "").Trim();
                    Console.WriteLine("Company: " + company);
                }

                if (setNode != null)
                {
                    setName = setNode.InnerText.Trim();
                    Console.WriteLine("Set: " + setName);
                }
            }
        } 

        // Do stuff here with the 

        // Clear the main document to free up memory
        doc?.DocumentNode.RemoveAll();

        setNum += 1;
        if (setNum < 99999)
        {
            Console.WriteLine($"");
            Console.WriteLine($"On set #: {setNum}");
            // Force garbage collection before the next recursive call
            GC.Collect();
            GC.WaitForPendingFinalizers();
            ScrapeTypeYearName(setNum);
        }
    }
}

У вас есть рекурсивный вызов метода ScrapeTypeYearName(), это намеренно?

Progman 20.05.2024 01:25

Да, он увеличивает setNum на единицу и просто перезапускает функцию в конце. Является ли это причиной рекурсивности внутри функции? Я не знаю, почему GC все равно не очищает память.

Ethan Allen 20.05.2024 01:38

GC может очистить только управляемую память, на которую больше нет ссылок. Вы уверены, что doc?.DocumentNode.RemoveAll() действительно удаляет ссылки?

Enigmativity 20.05.2024 01:46
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
70
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проблема в том, что вы вызываете ScrapeTypeYearName() рекурсивно. Каждый вызов создает новый стек с новыми локальными переменными метода, который вы вызываете. Это также означает, что все «предыдущие» стеки методов «приостанавливаются», но не удаляются и не «восстанавливаются» сборщиком мусора. У вас есть переменные setUrl, category, year, company, setName и breadcrumb, которые ссылаются на объекты в куче. И у вас есть эти ссылки в этих переменных для каждого стека методов. А поскольку у вас есть стек вызовов методов, состоящий из 99999-16020=83979 стеков методов, и шесть переменных, содержащих потенциально разные объекты в куче, в конечном итоге у вас закончилось место в куче.

Нет необходимости вызывать этот метод ScrapeTypeYearName() рекурсивно. Удалите рекурсивный вызов. Простой цикл for вне этого метода вполне подойдет. Код может быть таким простым, как:

for (int setNum=16020; setNum<99999; setNum++)
{
    ScrapeTypeYearName(setNum);
}

Хорошо, спасибо, что объяснили! Я обязательно добавлю обратный вызов (или какой-нибудь обработчик завершения), прежде чем продолжить цикл, потому что я не хочу, чтобы 99999 веб-вызовов происходили одновременно. Я протестирую еще раз и посмотрю, что произойдет.

Ethan Allen 20.05.2024 03:41

Переместил цикл наружу и добавил async/await, и это исправило ситуацию. Большое спасибо за помощь.

Ethan Allen 20.05.2024 03:58

Другие вопросы по теме