我在哪里有内存泄漏以及如何修复它?为什么内存消耗会增加?
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 Core 和 SqlConnection。两者都有相似的结果。您可以在下面找到它们。
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。
所以,这是需要做的。在弄清楚哪些对象的数量或大小随时间增加后,您可以找到创建这些对象的位置并优化此操作。
几天来我一直在努力解决 .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 Core 和 SqlConnection。两者都有相似的结果。您可以在下面找到它们。
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。
所以,这是需要做的。在弄清楚哪些对象的数量或大小随时间增加后,您可以找到创建这些对象的位置并优化此操作。