2012-01-04 33 views
81

短缺问题为什么一个循环过程中追加到TextBox.Text占用更多的内存每次迭代?

我有一个运行18万次的循环。在每次迭代结束时,它应该将结果附加到实时更新的TextBox。

使用MyTextBox.Text += someValue导致吃大量内存的应用程序,它经过几千年的记录耗尽可用内存。

是否有附加文本到TextBox.Text 18万次的更有效的方法?

编辑我真的不关心这种特殊情况下的结果,但是我想知道为什么这似乎是一个内存猪,如果有将文本追加到一个TextBox更有效的方式。


龙(原件)问题

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

我目前正在运行针对18万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.Text为未来的项目。

+7

你可以尝试使用'StringBuilder'来追加文本,然后在完成时将'StringBuilder'值赋给文本框。 – keyboardP 2012-01-04 20:59:02

+0

@keyboardP我忘了提到在后台线程上运行的循环,并且结果文本框实时更新 – Rachel 2012-01-04 21:01:06

+1

我不知道它是否会改变任何事情,但如果你有一个StringBuilder会附加新的Id-s并且您将使用一个属性,该属性使用字符串构建器的新值进行更新,并将其绑定到textbox.text属性。 – BigL 2012-01-04 21:01:37

回答

119

我怀疑内存使用量如此之大的原因是因为文本框保留了一个堆栈,以便用户可以撤消/重做文本。您的情况似乎不需要该功能,因此请尝试将IsUndoEnabled设置为false。

+1

从MSDN链接:“内存泄漏 如果你有一个内存增加在您的应用程序中,因为您经常从代码中设置值,所以textblock的撤消堆栈可能是内存的“泄漏”。通过使用此属性,您可以禁用它并清除泄漏内存的方式。 – wave 2012-01-04 22:00:55

+33

大多数时候,用户和开发人员都希望文本框能像标准文本框一样工作(即可以撤销/重做)。在诸如OP的要求等边缘情况下,它可能被证明是一种障碍。如果大多数人使用它,那么它应该是默认的。为什么你会期望一个边缘案例强制标准功能成为选择? – keyboardP 2012-01-04 22:22:13

+1

或者,您还可以将'UndoLimit'设置为实际值。默认值-1表示无限制堆栈。零(0)也将禁用撤消。 – 2012-07-18 14:35:13

9

不要直接附加到文本属性。使用StringBuilder进行追加,然后完成后,将.text设置为字符串构建器中的完成字符串

+2

我忘了提及循环运行在后台线程和结果得到实时更新 – Rachel 2012-01-04 21:01:26

5

而不是使用文本框的我会做到以下几点:

  1. 打开一个文本文件,以防万一流的错误日志文件。
  2. 使用列表框控件来表示错误以避免复制潜在的大量字符串。
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的限度内。

4

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

尽管如此,我能找到的最好是this reference question这个特定String.Format vs. StringBuilder问题,其中提到,String.Format使用StringBuilder内部。这让我怀疑你的记忆猪是否在别处。

**基于詹姆斯的评论,我要指出,我从来不做重字符串格式化,因为我专注于基于网络的发展。*

+0

我同意,有时人们会说“总是使用X cuz X是最好的”,这通常是过分简单化。 string.Concat(),string.Format()和StringBuilder之间有很多细微之处。我的经验法则是使用它的目的(听起来很愚蠢,我知道,但它是真实的)。我在连接字符串(然后立即使用结果)时使用concat,当我执行非重要的字符串格式(填充,数字格式等)时使用Format,以及用于在循环期间构建字符串的StringBuilder在循环结束时使用。 – 2012-01-04 21:12:47

+0

@JamesMichaelHare,这对我有意义;你在暗示使用'string.Format' /'StringBuilder'更合适吗? – jwiscarson 2012-01-04 21:14:54

+0

哦,不,我只是同意你的一般观点,即concat通常最适合简单字符串连接。 “经验法则”的问题在于,如果BCL发生变化,它们可以从.NET版本更改为版本,因此坚持逻辑正确的构造更易于维护,并且通常对其任务执行得更好。我其实有一个较早的博客文章,我在这里比较了三个:http://geekswithblogs.net/BlackRabbitCoder/archive/2010/05/10/c-string-compares-and-concatenations.aspx – 2012-01-04 21:17:43

3

可能重新考虑这个文本框?包含字符串Items的ListBox可能会更好。

但主要问题似乎是要求,显示180,000项不能针对(人类)用户,也不能在“实时”中进行更改。

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

当你确实想把它转储到可怜的用户时,批量字符串更新。没有用户每秒可以看到2或3次以上的变化。因此,如果您生产100个/秒,则使群组数量为50.

+0

谢谢Henk。这是一次性的事情,所以在写作时我很懒。我想要一些视觉输出来知道状态是什么,我想要文本选择功能和ScrollBar。我想我可以使用ScrollViewer/Label,但TextBoxes内置ScrollBarrs。我没想到它会导致问题:) – Rachel 2012-01-04 21:20:37

1

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

2

一些反应已经提到它,但没有人直接说出这是令人惊讶的。 字符串是不可变的,这意味着字符串在创建后无法修改。因此,每次连接到一个现有的String时,都需要创建一个新的String对象。与该String对象关联的内存显然也需要创建,随着您的Strings变得越来越大,这可能会变得昂贵。在大学里,我曾经在做过霍夫曼编码压缩的Java程序中犯下了连接字符串的业余错误。当你连接了大量的文本时,当你可以简单地使用StringBuilder时,字符串连接会真正地伤害你,正如这里提到的一些。

0

未提及的一点是,即使您在后台线程中执行操作,UI元素本身的更新也必须在主线程本身(无论如何都在WinForms中)发生。

更新您的文本框,你有看起来像

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

如果是这样,那么你的后台运算肯定是由UI更新瓶颈的任何代码。

我建议你的背景运算使用StringBuilder如上所述,但不是更新文本框每个周期,尝试定期更新它,看它是否提高性能为您服务。

编辑注:有没有使用过WPF。

2

按照建议使用StringBuilder。 尝试估算最终的字符串大小,然后在实例化StringBuilder时使用该数字。 StringBuilder sb = new StringBuilder(estSize);

当更新文本框只是用分配例如:textbox.text = sb.ToString();

观察上述的跨线程操作。但是使用BeginInvoke。当UI更新时,不需要阻止 后台线程。

14

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

,看看如何与此相比,IsUndoEnabled标志从keyboardP的答案会很有趣。

+0

对于Windows窗体,这是最好的解决方案,因为windows窗体没有TextBox.IsUndoEnabled – BrDaHa 2013-07-31 17:13:50

+0

在Win窗体中,你有一个'bool CanUndo'属性 – imlokesh 2015-04-12 20:34:35

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。在这里,您可以添加新字符串而不创建大字符串。 A StringBuild不起作用,因为您还想显示中间结果。

相关问题