我已经苦苦挣扎了几天,.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()
,这也无济于事。请注意,下面的方法会触发很多parralel 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.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
,其中是更新的DB。我尝试了两种方法,分别使用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);
}
主要问题是,在成千上万的更新模型之后,我的控制台应用程序的内存消耗持续增加。而且我不知道为什么。
SOLUTION我的问题出在ScrapSingleModelAsync
方法内部,其中我错误地使用了HtmlAgilityPack
,由于有了[[cassandrad,我可以调试什么。
考虑使用性能分析工具,例如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();
当函数至少运行一次时,拍摄第一个快照。就我而言,我在第一个巨大空间分配完毕后拍摄了快照。此后,让您的应用程序正常工作一段时间,以便显着提高内存使用率的差异,请拍摄第二个内存快照。
您会注意到,添加了另一个快照,其中包含有关差异的信息。要获取更多特定信息,请单击最新快照的一个或另一个蓝色标签以打开快照比较。
根据我的示例,我们可以看到int数组的数量发生了变化。默认情况下,int []在表中不可见,因此我必须在过滤选项中取消选中Just My Code。因此,这是需要做的。确定了哪些对象随时间增加或增加数量后,可以找到创建这些对象的位置并优化此操作。