我在哪里有内存泄漏以及如何修复它?为什么内存消耗会增加?

Where do I have memory leaks and how to fix it? Why memory consumption increases?

几天来我一直在努力解决 .Net Core 2.2 中控制台应用程序内存消耗增加的问题,而现在我 运行 不知道我还能改进什么。

在我的应用程序中,我有一个触发 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 创建 'repo' 并且实例从数据库中获取要更新的对象列表(大约 200k):

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(),什么也没有帮助。请注意,下面的方法会触发大量并行 Tasks,但它们应该在其中处理,对吗?:

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.ScrapeSingleModelAsync(some_list[iteration].Mmsi);
                                while (Updating)
                                {
                                    await Task.Delay(1000);
                                }
                                if (model != null)
                                {
                                    lock (((ICollection)vessels).SyncRoot)
                                    {
                                        vessels.Add(model);

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

                            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,其中更新了数据库。我尝试了两种方法,使用 EC CoreSqlConnection。两者都有相似的结果。您可以在下面找到它们。

EF 核心

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);
        }

主要问题是,在 tenths/hundreds 更新了数千个模型后,我的控制台应用程序内存消耗不断增加。我不知道为什么。

SOLUTION 我的问题出在 ScrapeSingleModelAsync 方法内部,我在那里使用不正确 HtmlAgilityPack,感谢 我可以调试的内容卡桑德拉.

你的代码很乱,有大量不同的对象,生命周期未知。光看问题是很难弄清楚的。

考虑使用性能分析工具,例如 Visual Studio 的 Diagnostic Tools,它们将帮助您找出哪些对象在堆中存活时间过长。 Here 概述了其与内存分析相关的功能。 强烈推荐阅读

简而言之,您需要拍两张快照,看看哪些对象占用的内存最多。让我们看一个简单的例子。

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

当您的函数至少运行一次时拍摄第一张快照。在我的例子中,我在第一个巨大的 space 被分配时拍摄了快照。

之后,让您的应用运行一段时间,以便内存使用量的差异变得明显,拍摄第二个内存快照。

您会注意到添加了另一个快照,其中包含有关差异有多大的信息。要获取更具体的信息,请单击最新快照的一个或另一个蓝色标签以打开快照比较。

按照我的例子,我们可以看到int数组的数量发生了变化。默认情况下,int[] 在 table 中不可见,所以我不得不在过滤选项中取消选中 Just My Code。
所以,这是需要做的。在弄清楚哪些对象的数量或大小随时间增加后,您可以找到创建这些对象的位置并优化此操作。