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

时间:2012-01-04 20:56:37

标签: c# wpf

简短问题

我有一个运行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以用于将来的项目。

12 个答案:

答案 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)

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

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

答案 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无效,因为您也想显示中间结果。