首页 文章

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

提问于
浏览
82

Short Question

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

使用 MyTextBox.Text += someValue 会导致应用程序占用大量内存,并且在几千条记录之后它耗尽了可用内存 .

有没有更有效的方法将文本附加到180,000次_166349?

Edit 我真的不关心这个特定情况的结果,但是我想知道为什么这似乎是一个内存耗尽,并且如果有一种更有效的方法将文本附加到TextBox .


Long (Original) Question

我有一个小应用程序,它读取CSV文件中的ID号列表,并为每个应用程序生成PDF报告 . 生成每个pdf文件后, ResultsTextBox.Text 会附加已处理的报告的ID号,并且已成功处理该报告 . The process runs on a background thread, so the ResultsTextBox gets updated real-time as items get processed

我目前正在针对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 回答

  • 4

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

  • 2

    使用 TextBox.AppendText(someValue) 而不是 TextBox.Text += someValue . 它's easy to miss since it'在TextBox上,而不是TextBox.Text . 与StringBuilder一样,这将避免在每次添加内容时创建整个文本的副本 .

    看看这与keyboardP的答案中的 IsUndoEnabled 标志相比如何,这将会很有趣 .

  • 14

    不要直接附加到text属性 . 使用StringBuilder进行追加,然后在完成后,将.text设置为stringbuilder中的完成字符串

  • 9

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

    • 打开文本文件并将错误流式传输到日志文件以防万一 .

    • 使用列表框控件来表示错误,以避免复制潜在的大量字符串 .

  • 5

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

    尽管如此,我能找到的最好的是this reference question和这个特定的String.Format vs. StringBuilder问题,其中提到 String.Format 在内部使用 StringBuilder . 这让我想知道你的记忆力是否在其他地方 .

    **基于James的评论,我应该提到我从不做重字符串格式化,因为我专注于基于Web的开发 . *

  • 119

    也许重新考虑TextBox?包含字符串Items的ListBox可能会表现得更好 .

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

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

    当您想要将其转储给糟糕的用户时,批量字符串更新 . 没有用户可以每秒识别超过2或3个更改 . 因此,如果你产生100 /秒,那么组成50 .

  • 1

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

  • 3

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

    更新TextBox时,只需使用赋值,例如:textbox.text = sb.ToString();

    注意上面的跨线程操作 . 但是请使用BeginInvoke . UI更新时无需阻止后台线程 .

  • 2

    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限制 .

  • 0

    ViewModel 中的StringBuilder将避免字符串重新绑定混乱并将其绑定到 MyTextBox.Text . 此方案将多次提高性能并减少内存使用量 .

  • 0

    没有提到的是,即使你在后台线程中执行操作,UI元素本身的更新也要发生在主线程本身上(无论如何都在WinForms中) .

    更新文本框时,您是否有任何类似的代码

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

    如果是这样,那么你的后台操作肯定会受到UI更新的瓶颈 .

    我建议您的后台操作如上所述使用StringBuilder,但不是每个周期更新文本框,请尝试定期更新它以查看它是否为您提高性能 .

    编辑注意:没有使用WPF .

  • 1

    你说记忆成倍增长 . 不,它是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 没有帮助,因为您也想显示中间结果 .

相关问题