Где у меня утечки памяти и как это исправить? Почему увеличивается потребление памяти? - PullRequest
0 голосов
/ 24 апреля 2020

Я уже несколько дней борюсь с проблемой растущего потребления памяти консольным приложением в. Net Core 2.2, и только сейчас у меня закончились идеи, что еще я мог бы улучшить.

Im my Приложение У меня есть метод, который запускает метод StartUpdatingAsync:

public MenuViewModel()
        {
            if (File.Exists(_logFile))
                File.Delete(_logFile);

            try
        {
            StartUpdatingAsync("basic").GetAwaiter().GetResult();
        }
        catch (ArgumentException aex)
        {
            Console.WriteLine($"Caught ArgumentException: {aex.Message}");
        }

            Console.ReadKey();
        }

StartUpdatingAsync создает репо, и экземпляр получает из БД список объектов для обновления (около 200 КБ):

private async Task StartUpdatingAsync(string dataType)
        {
            _repo = new DataRepository();
            List<SomeModel> some_list = new List<SomeModel>();
            some_list = _repo.GetAllToBeUpdated();

            await IterateStepsAsync(some_list, _step, dataType);
        }

И теперь, в IterateStepsAsync мы получаем обновления, анализируем их с существующими данными и обновляем БД. Внутри каждого while я создавал новые экземпляры всех новых классов и списков, чтобы быть уверенным, что старые освобождают память, но это не помогло. Также я был GC.Collect() в конце метода, что также не помогает. Обратите внимание, что приведенный ниже метод вызывает много парралей Задачи , но они должны быть расположены внутри него, я прав?:

private async Task IterateStepsAsync(List<SomeModel> some_list, int step, string dataType)
        {
            List<Area> areas = _repo.GetAreas();
            int counter = 0;

            while (counter < some_list.Count)
            {
                _repo = new DataRepository();
                _updates = new HttpUpdates();
                List<Task> tasks = new List<Task>();
                List<VesselModel> vessels = new List<VesselModel>();
                SemaphoreSlim throttler = new SemaphoreSlim(_degreeOfParallelism);

                for (int i = counter; i < step; i++)
                {
                    int iteration = i;
                    bool skip = false;

                    if (dataType == "basic" && (some_list[iteration].Mmsi == 0 || !some_list[iteration].Speed.HasValue)) //if could not be parsed with "full"
                        skip = true;

                    tasks.Add(Task.Run(async () =>
                    {
                        string updated= "";
                        await throttler.WaitAsync();
                        try
                        {
                            if (!skip)
                            {
                                Model model= await _updates.ScrapSingleModelAsync(some_list[iteration].Mmsi);
                                while (Updating)
                                {
                                    await Task.Delay(1000);
                                }
                                if (model != null)
                                {
                                    lock (((ICollection)vessels).SyncRoot)
                                    {
                                        vessels.Add(model);

                                        scrapped = BuildData(model);
                                    }
                                }
                            }
                            else
                            {
                                //do nothing
                            }
                        }
                        catch (Exception ex)
                        {
                            Log("Scrap error: " + ex.Message);
                        }
                        finally
                        {
                            while (Updating)
                            {
                                await Task.Delay(1000);
                            }
                            Console.WriteLine("Updates for " + counter++ + " of " + some_list.Count + scrapped);

                            throttler.Release();
                        }

                    }));
                }

                try
                {
                    await Task.WhenAll(tasks);
                }
                catch (Exception ex)
                {
                    Log("Critical error: " + ex.Message);
                }
                finally
                {
                    _repo.UpdateModels(vessels, dataType, counter, some_list.Count, _step);

                    step = step + _step;

                    GC.Collect();
                }
            }
        }

Внутри метода выше мы вызываем _repo.UpdateModels, где обновляется БД. Я попробовал два подхода, используя E C Core и SqlConnection . Оба с похожими результатами. Ниже вы можете найти их обоих.

EF Core

internal List<VesselModel> UpdateModels(List<Model> vessels, string dataType, int counter, int total, int _step)
        {

            for (int i = 0; i < vessels.Count; i++)
            {
                Console.WriteLine("Parsing " + i + " of " + vessels.Count);

                Model existing = _context.Vessels.Where(v => v.id == vessels[i].Id).FirstOrDefault();
                if (vessels[i].LatestActivity.HasValue)
                {
                    existing.LatestActivity = vessels[i].LatestActivity;
                }
                //and similar parsing several times, as above
            }

            Console.WriteLine("Saving ...");
            _context.SaveChanges();
            return new List<Model>(_step);
        }

SqlConnection

internal List<VesselModel> UpdateModels(List<Model> vessels, string dataType, int counter, int total, int _step)
        {
            if (vessels.Count > 0)
            {
                using (SqlConnection connection = GetConnection(_connectionString))
                using (SqlCommand command = connection.CreateCommand())
                {
                    connection.Open();
                    StringBuilder querySb = new StringBuilder();

                    for (int i = 0; i < vessels.Count; i++)
                    {
                        Console.WriteLine("Updating " + i + " of " + vessels.Count);
                        //PARSE

                        VesselAisUpdateModel existing = new VesselAisUpdateModel();

                        if (vessels[i].Id > 0)
                        {
                            //find existing
                        }

                        if (existing != null)
                        {
                            //update for basic data
                            querySb.Append("UPDATE dbo." + _vesselsTableName + " SET Id = '" + vessels[i].Id+ "'");
                            if (existing.Mmsi == 0)
                            {
                                if (vessels[i].MMSI.HasValue)
                                {
                                    querySb.Append(" , MMSI = '" + vessels[i].MMSI + "'");
                                }
                            }
                            //and similar parsing several times, as above

                            querySb.Append(" WHERE Id= " + existing.Id+ "; ");

                            querySb.AppendLine();
                        }
                    }

                    try
                    {
                        Console.WriteLine("Sending SQL query to " + counter);
                        command.CommandTimeout = 3000;
                        command.CommandType = CommandType.Text;
                        command.CommandText = querySb.ToString();
                        command.ExecuteNonQuery();
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                    }
                    finally
                    {
                        connection.Close();
                    }
                }
            }
            return new List<Model>(_step);
        }

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

РЕШЕНИЕ моя проблема была в методе ScrapSingleModelAsync, где я неправильно использовал HtmlAgilityPack, что я мог отлаживать благодаря cassandrad .

1 Ответ

1 голос
/ 24 апреля 2020

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

Рассмотрите возможность использования инструментов профилирования, например Visual Studio Diagnostic Tools, они помогут вам найти объекты, которые живут слишком долго в куче. Здесь - обзор его функций, связанных с профилированием памяти. Настоятельно рекомендуется прочитать .

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

int[] first = new int[10000];
Console.WriteLine(first.Length);
int[] secod = new int[9999];
Console.WriteLine(secod.Length);
Console.ReadKey();

Сделайте первый снимок, когда ваша функция работает хотя бы один раз. В моем случае я сделал снимок, когда было выделено первое огромное пространство. enter image description here

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

enter image description here Вы заметите, что добавлен еще один снимок с информацией о разнице. Чтобы получить более подробную информацию c, щелкните по той или иной синей метке последнего снимка, чтобы открыть сравнение снимков.

enter image description here

Следуя моему примеру, мы можем видеть, что есть изменение в количестве массивов int. По умолчанию int [] не было видно в таблице, поэтому мне пришлось снять флажок «Просто мой код» в параметрах фильтрации.
Итак, это то, что нужно сделать. После того, как вы выясните, какие объекты увеличиваются в количестве или размере с течением времени, вы можете определить местонахождение этих объектов и оптимизировать эту операцию.

...