简短问题
我有一个运行180,000次的循环。在每次迭代结束时,它应该将结果附加到TextBox,TextBox会实时更新。
使用MyTextBox.Text += someValue
导致应用程序占用大量内存,并且在几千条记录之后它耗尽了可用内存。
是否有更有效的方式将文字附加到TextBox.Text
180,000次?
编辑我真的不关心这个特定情况的结果,但我想知道为什么这似乎是一个内存耗尽,如果有更有效的方法来追加文本到一个TextBox。
长(原创)问题
我有一个小应用程序,它读取CSV文件中的ID号列表,并为每个文件生成PDF报告。生成每个pdf文件后,ResultsTextBox.Text
会附加已处理的报告的ID号以及已成功处理的报告的ID号。 该进程在后台线程上运行,因此在处理项目时,ResultsTextBox会实时更新
我目前正在针对180,000个ID号运行该应用程序,但是应用程序占用的内存随着时间的推移呈指数级增长。它从大约90K开始,但是大约3000条记录占用大约250MB,而4000条记录占用大约500 MB的内存。
如果我注释掉结果文本框的更新,内存保持相对静止大约90K,所以我可以假设写ResultsText.Text += someValue
是导致它吃内存的原因。
我的问题是,这是为什么?将数据附加到不占用内存的TextBox.Text的更好方法是什么?
我的代码如下所示:
try
{
report.SetParameterValue("Id", id);
report.ExportToDisk(ExportFormatType.PortableDocFormat,
string.Format(@"{0}\{1}.pdf", new object[] { outputLocation, id}));
// ResultsText.Text += string.Format("Exported {0}\r\n", id);
}
catch (Exception ex)
{
ErrorsText.Text += string.Format("Failed to export {0}: {1}\r\n",
new object[] { id, ex.Message });
}
还应该值得一提的是,该应用程序是一次性的事情并不需要花费几个小时(或几天:)来生成所有报告。我主要担心的是,如果它达到系统内存限制,它将停止运行。
我可以将更新结果TextBox的行更改为运行此内容,但我想知道是否有更高效的内存方式将数据附加到TextBox.Text
以用于将来的项目。
答案 0 :(得分:119)
我怀疑内存使用量如此之大的原因是因为文本框维护了一个堆栈,以便用户可以撤消/重做文本。在您的情况下似乎不需要该功能,因此请尝试将IsUndoEnabled
设置为false。
答案 1 :(得分:14)
使用TextBox.AppendText(someValue)
代替TextBox.Text += someValue
。它很容易被遗漏,因为它在TextBox上,而不是TextBox.Text。与StringBuilder一样,每次添加内容时都会避免创建整个文本的副本。
看看这与来自keyboardP的答案的IsUndoEnabled
标志的比较会很有趣。
答案 2 :(得分:9)
不要直接附加到text属性。使用StringBuilder进行追加,然后完成后,将.text设置为stringbuilder中的完成字符串
答案 3 :(得分:5)
我不会使用文本框,而是执行以下操作:
答案 4 :(得分:4)
就个人而言,我总是使用string.Concat
*。我记得几年前在Stack Overflow上阅读了一个问题,该问题包含了比较常用方法的分析统计数据,并且(似乎)回想起string.Concat
赢了。
尽管如此,我能找到的最好的是this reference question和这个特定的String.Format
vs. StringBuilder
问题,其中提到String.Format
在内部使用StringBuilder
。这让我想知道你的记忆力是否在其他地方。
**基于詹姆斯的评论,我应该提到我从不做重字符串格式化,因为我专注于基于网络的开发。*
答案 5 :(得分:3)
也许重新考虑TextBox?包含字符串Items的ListBox可能会表现得更好。
但主要问题似乎是要求,显示180,000个项目不能针对(人类)用户,也不会在“实时”中更改它。
最好的方法是显示数据样本或进度指示器。
如果您想将其转储给糟糕的用户,批量字符串更新。没有用户可以每秒识别超过2或3个更改。因此,如果你产生100 /秒,则制作50组。
答案 6 :(得分:2)
有些回应已经提到过,但没有人直截了当地说出这是令人惊讶的。 字符串是不可变的,这意味着String在创建后无法修改。因此,每次连接到现有String时,都需要创建一个新的String对象。显然需要创建与该String对象关联的内存,随着您的字符串变得越来越大,这可能会变得昂贵。在大学里,我曾经做过业余的错误,在一个Java程序中连接Strings,这个程序做了Huffman编码压缩。当你连接非常大量的文本时,当你可以简单地使用StringBuilder时,字符串连接真的会伤害你,正如这里提到的那样。
答案 7 :(得分:2)
按照建议使用StringBuilder。 尝试估计最终的字符串大小,然后在实例化StringBuilder时使用该数字。 StringBuilder sb = new StringBuilder(estSize);
更新TextBox时,只需使用赋值,例如:textbox.text = sb.ToString();
注意上面的跨线程操作。但是请使用BeginInvoke。无需阻止 UI更新时的后台线程。
答案 8 :(得分:1)
A)简介:已经提到过,请使用StringBuilder
B)要点:不要太频繁地更新,即
DateTime dtLastUpdate = DateTime.MinValue;
while (condition)
{
DoSomeWork();
if (DateTime.Now - dtLastUpdate > TimeSpan.FromSeconds(2))
{
_form.Invoke(() => {textBox.Text = myStringBuilder.ToString()});
dtLastUpdate = DateTime.Now;
}
}
C)如果是一次性工作,请使用x64架构以保持2Gb限制。
答案 9 :(得分:1)
ViewModel
中的 StringBuilder
将避免字符串重新绑定混乱并将其绑定到MyTextBox.Text
。此方案将多次提高性能并减少内存使用量。
答案 10 :(得分:0)
未提及的一点是,即使您在后台线程中执行操作,UI元素本身的更新也会发生在主线程本身上(无论如何都在WinForms中)。
更新文本框时,您是否有任何类似
的代码if(textbox.dispatcher.checkAccess()){
textbox.text += "whatever";
}else{
textbox.dispatcher.invoke(...);
}
如果是这样,那么你的后台操作肯定会受到UI更新的瓶颈。
我建议您的后台操作使用StringBuilder,如上所述,但不是每个周期都更新文本框,请尝试定期更新它以查看它是否会为您提高性能。
编辑注意:尚未使用WPF。
答案 11 :(得分:0)
你说记忆成倍增长。不,它是quadratic growth,即多项式增长,不像指数增长那样引人注目。
您正在创建包含以下项目数的字符串:
1 + 2 + 3 + 4 + 5 ... + n = (n^2 + n) /2.
使用n = 180,000
,您可以获得16,200,090,000 items
的总内存分配,即16.2 billion items
!这个内存不会立刻分配,但GC(垃圾收集器)的清理工作很多!
另外,请记住,前一个字符串(正在增长)必须复制到新字符串179,999次。复制的字节总数也与n^2
一致!
正如其他人所建议的那样,请使用ListBox。在这里,您可以添加新字符串而无需创建大字符串。 StringBuild
无效,因为您也想显示中间结果。