【发布时间】:2021-12-21 22:10:54
【问题描述】:
背景:
我正在下载一个大型 (>500mb) 文本文件,其中包含我需要在数据库中运行的大量 SQL 语句。为此,我逐行处理文件,直到找到完整的查询然后执行它。运行此应用程序时,while 循环内的逻辑使用的内存比预期的要多。
我已经删除了对数据库运行查询的代码 - 调试后似乎不是导致问题的原因。
代码:
下面是一些示例代码来演示 - 显然这不是完整的程序,但这是我将问题缩小到的地方。请注意,sr 是一个 StreamReader,它已初始化为从我的 MemoryStream 读取。
StringBuilder query = new StringBuilder();
while (!sr.EndOfStream)
{
query.AppendLine(await sr.ReadLineAsync());
string currentQueryString = query.ToString();
if (currentQueryString.EndsWith($"{Environment.NewLine}GO{Environment.NewLine}"))
{
// Run query against database
// Clean up StringBuilder so it can be used again
query = new StringBuilder();
currentQueryString = "";
}
}
对于这个例子,假设文件中的每个新行的长度都在 1 到 300 个字符之间。此外,99% 的查询是 INSERT 语句,包含 1,000 条记录(每条记录在一个新行上)。
当我运行应用程序时:
我可以在我的 Windows 任务管理器中看到,随着应用程序的运行,分配给应用程序的内存会增加,看起来几乎每次 while 循环迭代。我在currentQueryString = ""; 上放置了一个断点,每次它被击中(知道我刚刚将文件的另外 1,000 行读入内存)我可以看到应用程序使用的内存增加了(这次是使用诊断工具在 Visual Studio 中)大约从 100mb 到 200mb 不等,但是从每次遇到断点时拍摄快照,我可以看到堆大小几乎没有变化,无论哪种方式都可能有几百 kb。
我认为导致问题的原因:
目前我最好的猜测是string currentQueryString = query.ToString(); 行以某种方式初始化了一个可能在未释放的非托管内存中的变量。原因之一是我使用以下代码进行了测试,该代码删除了对 StringBuilder 的调用 toString(),并且内存使用量大大降低,因为每处理 1,000 行它只会增加大约 1-2mb 左右:
while (timer.Elapsed.TotalMinutes < 14 && !sr.EndOfStream && !killSwitch)
{
query.AppendLine(await sr.ReadLineAsync());
currentExeQueryCounter += 1;
if (currentExeQueryCounter > 1000)
{
query = new StringBuilder();
currentExeQueryCounter = 0;
}
}
仅出于调试目的,我在第一个代码 sn-p 中的 currentQueryString = ""; 下面添加了 GC.Collect(),它完全解决了问题(在 Visual Studio 诊断工具和任务管理器中都观察到),我试图理解为什么会这样以及如何最好地解决这个问题,因为我的目标是将其作为一个无服务器应用程序运行,该应用程序将分配有限的内存。
【问题讨论】:
-
如果
GC.Collect()“解决”了问题。没有问题.. -
让我们从基础开始。您是否有实际问题,或者您只是发现它使用的内存比您想象的要多?后者不是实际问题,除非它导致崩溃、减速等。
-
而不是
new StringBuilder为什么不query.Clear() -
关于使用
query.Clear()的 cmets 我之前实际上正在使用它,但看到另一篇帖子建议尝试将其设置为null。当我这样做时,老实说,我只是抓着稻草——最后对我来说并没有什么不同,我只是忘了把它改回来。不过,我现在又重新使用query.Clear()
标签: c# .net memory memory-leaks