为什么在循环期间追加到TextBox.Text会在每次迭代中占用更多的内存?

简短的问题

我有一个循环,运行18万次。 在每次迭代结束时,应该将结果追加到实时更新的文本框中。

使用MyTextBox.Text += someValue导致应用程序吃了大量的内存,并且在几千条logging之后耗尽可用内存。

有一种更有效的方式将文本附加到TextBox.Text 180,000次?

编辑我真的不关心这个具体案件的结果,但是我想知道为什么这似乎是一个记忆猪,如果有一个更有效的方式将文本追加到一个文本框。


长(原始)问题

我有一个小应用程序,它读取一个CSV文件中的ID号列表,并为每一个生成一个PDF报告。 生成每个pdf文件后, ResultsTextBox.Text会附加上已处理的报告的ID号,并成功处理。 该进程在后台线程上运行,所以ResultsTextBox在项目被处理时被实时更新

我目前正在运行该应用程序对180,000个ID号码,但应用程序正在使用的内存随着时间的推移呈指数级增长。 它大约从90K开始,但是大约有3000个logging占据了大约250MB,而4000个logging占用了大约500MB的内存。

如果我注释掉结果文本框的更新,内存在大约90K的时候保持相对稳定,所以我可以假设写ResultsText.Text += someValue文本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为未来的项目。

我怀疑内存使用量如此之大的原因是因为文本框保持一个堆栈,以便用户可以撤消/重做文本。 这个function似乎并不需要你的情况,所以尝试设置IsUndoEnabled为false。

使用TextBox.AppendText(someValue)而不是TextBox.Text += someValue 。 因为它在TextBox上,而不是TextBox.Text,所以很容易丢失。 像StringBuilder一样,这将避免每次添加内容时创build整个文本的副本。

看到这与来自keyboardP的答案的IsUndoEnabled标志相比如何是有趣的。

不要直接附加到文本属性。 使用StringBuilder进行追加,然后完成后,将.text设置为stringbuilder中的完成string

而不是使用文本框,我会执行以下操作:

  1. 打开一个文本文件并将错误stream传输到日志文件以防万一。
  2. 使用列表框控件来表示错误,以避免复制潜在的大量string。

就个人而言,我总是使用string.Concat *。 我记得在Stack Overflow多年前的一个问题上看到一个问题,那就是比较常用的方法,并且(似乎)回想起string.Concat赢了。

尽pipe如此,我能find的最好的是这个参考问题和这个特定的String.FormatStringBuilder问题,它提到了String.Format在内部使用StringBuilder 。 这让我怀疑你的记忆猪是否在别处。

**根据詹姆斯的评论,我应该提到,我从来没有做重string格式化,因为我专注于基于Web的开发。

也许重新考虑TextBox? 包含string项目的ListBox可能会更好。

但主要问题似乎是要求,显示18万个项目不能针对(人)用户,也不是以“实时”来改变它。

最好的方法是显示数据样本或进度指示器。

当你想把它转储到可怜的用户时,批量string更新。 没有用户每秒可以看到2或3次以上的变化。 所以如果你产生100个/秒,则可以分组50个。

有人提出了一些回应,但没有人直接表示这是令人惊讶的。 string是不可变的,这意味着string在创build后不能被修改。 因此,每次连接到一个现有的String时,都需要创build一个新的String对象。 与该String对象关联的内存显然也需要创build,随着string变得越来越大,这可能会变得昂贵。 在大学里,我曾经在一个做了霍夫曼编码压缩的Java程序中做了连接string的业余错误。 当你连接了大量的文本时,当你可以简单地使用StringBuilder的时候,String concatenation真的会伤到你,就像这里提到的那样。

build议使用StringBuilder。 尝试估计最终的string大小,然后在实例化StringBuilder时使用该数字。 StringBuilder sb = new StringBuilder(estSize);

当更新文本框只使用赋值如:textbox.text = sb.ToString();

注意如上所述的跨线程操作。 但是使用BeginInvoke。 UI更新时不需要阻止后台线程。

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的限制。

ViewModel StringBuilder将避免string重新混乱并将其绑定到MyTextBox.Text 。 这种情况下会多次提高性能,并减less内存使用量。

没有提到的是,即使你在后台线程中执行操作,UI元素本身的更新也必须在主线程本身(WinForms中)发生。

更新你的文本框时,你有没有看起来像的任何代码

 if(textbox.dispatcher.checkAccess()){ textbox.text += "whatever"; }else{ textbox.dispatcher.invoke(...); } 

如果是这样,那么你的后台操作肯定是由UI更新瓶颈。

我build议你的背景使用StringBuilder,如上所述,但不是每个周期更新文本框,而是定期更新它,看它是否会提高性能。

编辑注:有没有使用WPF。

你说记忆力呈指数增长。 不,这是一个二次增长 ,即一个多项式的增长,并不像指数增长那么戏剧化。

您正在创build包含以下项目数的string:

 1 + 2 + 3 + 4 + 5 ... + n = (n^2 + n) /2. 

n = 180,000你可以获得16,200,090,000 items总内存分配,即16.2 billion items 。 这个内存不会一次性分配,但对GC(垃圾回收器)来说,这是一个很大的清理工作!

另外,请记住,以前的string(正在增长)必须被复制到179,999次的新string中。 复制的字节总数也是n^2

正如其他人所build议的,改用ListBox。 在这里你可以添加新的string而不需要创build一个巨大的字符 一个StringBuild没有帮助,因为你也想显示中间结果。