Почему добавление к TextBox.Text во время цикла занимает больше памяти с каждой итерацией?

Короткий вопрос

У меня есть цикл, который работает 180 000 раз. В конце каждой итерации предполагается добавить результаты в TextBox, который обновляется в режиме реального времени.

Использование MyTextBox.Text += someValue приводит к тому, что приложение потребляет огромные объемы памяти, и после нескольких тысяч записей он исчерпывает доступную память.

Есть ли более эффективный способ добавления текста к TextBox.Text 180 000 раз?

Изменить Я действительно не забочусь о результате этого конкретного случая, однако я хочу знать, почему это похоже на ботовую память, и если есть более эффективный способ добавления текста в текстовое поле.


Длительный (оригинальный) вопрос

У меня есть небольшое приложение, которое читает список идентификационных номеров в CSV файле и генерирует отчет PDF для каждого из них. После создания каждого файла PDF ResultsTextBox.Text добавляется с идентификационным номером отчета, который был обработан и что он был успешно обработан. Процесс выполняется в фоновом потоке, поэтому ResultsTextBox обновляется в режиме реального времени, когда элементы обрабатываются

В настоящее время я запускаю приложение против 180 000 идентификационных номеров, однако память, которую приложение занимает, растет с экспоненциальной скоростью со временем. Он начинается примерно на 90 тыс., Но примерно на 3000 записей он занимает примерно 250 МБ, а на 4000 записей приложение занимает около 500 МБ памяти.

Если я закомментирую обновление для TextBox результатов, память остается относительно неподвижной примерно на 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 для будущих проектов.

Ответ 1

Я подозреваю, что использование памяти настолько велико, потому что текстовые файлы поддерживают стек, чтобы пользователь мог отменить/повторить текст. Эта функция, кажется, не требуется в вашем случае, поэтому постарайтесь установить IsUndoEnabled значение false.

Ответ 2

Используйте TextBox.AppendText(someValue) вместо TextBox.Text += someValue. Его легко пропустить, поскольку он находится в TextBox, а не TextBox.Text. Как и StringBuilder, это позволит не создавать копии всего текста каждый раз, когда вы что-то добавляете.

Было бы интересно посмотреть, как это сравнивается с флагом IsUndoEnabled с ответом на клавиатуру.

Ответ 3

Не добавляйте непосредственно к свойству text. Используйте StringBuilder для добавления, а затем, когда закончите, установите .text в законченную строку из stringbuilder

Ответ 4

Вместо использования текстового поля я бы сделал следующее:

  • Откройте текстовый файл и на всякий случай отправьте ошибки в файл журнала.
  • Используйте элемент управления списком для представления ошибок, чтобы избежать копирования потенциально массивных строк.

Ответ 5

Лично я всегда использую string.Concat *. Я помню, что читал вопрос здесь о Qaru несколько лет назад, который профилировал статистику, сравнивая часто используемые методы, и (кажется), чтобы вспомнить, что string.Concat выиграл.

Тем не менее, лучшее, что я могу найти, это этот ссылочный вопрос и этот конкретный String.Format vs. StringBuilder, в котором упоминается, что String.Format использует StringBuilder внутренне. Это заставляет меня задаться вопросом, находится ли ваша память в другом месте.

** на основе комментария Джеймса, я должен упомянуть, что я никогда не делаю тяжелого форматирования строк, поскольку я сосредотачиваюсь на веб-разработке. *

Ответ 6

Может быть, пересмотреть TextBox? Строка, содержащая ListBox Элементы, вероятно, будут работать лучше.

Но основная проблема - это требования. Показание 180 000 элементов не может быть нацелено на пользователя (человека) и не меняет его в "реальном времени".

Предпочтительным способом было бы показать образец данных или индикатор прогресса.

Когда вы захотите сбросить его на бедного пользователя, обновите пакетную строку. Ни один пользователь не может описать более 2 или 3 изменений в секунду. Поэтому, если вы производите 100/секунду, создайте группы из 50.

Ответ 7

Некоторые ответы ссылались на него, но никто не сказал об этом неожиданно. Строки неизменяемы, что означает, что строка не может быть изменена после ее создания. Поэтому каждый раз, когда вы соединяетесь с существующей строкой, необходимо создать новый объект String. Очевидно, что необходимо создать память, связанную с этим объектом String, которая может стать дорогой, поскольку ваши строки становятся больше и больше. В колледже я однажды совершил любительскую ошибку при объединении строк в Java-программе, которая сделала сжатие кодирования Хаффмана. Когда вы объединяете чрезвычайно большие объемы текста, конкатенация строк может действительно повредить вам, когда вы могли бы просто использовать StringBuilder, как упоминают некоторые из них.

Ответ 8

Используйте StringBuilder, как было предложено. Попытайтесь оценить окончательный размер строки, затем используйте это число при создании экземпляра StringBuilder. StringBuilder sb = new StringBuilder (estSize);

При обновлении TextBox просто используйте назначение, например: textbox.text = sb.ToString();

Следите за кросс-потоковыми операциями, как указано выше. Однако используйте BeginInvoke. Не нужно блокировать фоновый поток при обновлении пользовательского интерфейса.

Ответ 9

A) Intro: уже упоминалось, используйте 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, чтобы оставаться в пределах 2 ГБ.

Ответ 10

StringBuilder в ViewModel позволит избежать беспорядка переупорядочения строк и привязать его к MyTextBox.Text. Этот сценарий увеличит производительность во много раз и уменьшит использование памяти.

Ответ 11

Что-то, о чем не упоминалось, заключается в том, что даже если вы выполняете операцию в фоновом потоке, обновление самого элемента UI должно произойти в самом основном потоке (в WinForms).

При обновлении текстового поля у вас есть код, который выглядит как

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

Если это так, то ваш фоновый оператор определенно будет помехой обновлением пользовательского интерфейса.

Я бы предположил, что ваш фоновый оператор использует StringBuilder, как указано выше, но вместо обновления текстового поля каждый цикл попробуйте обновить его через равные промежутки времени, чтобы узнать, увеличивает ли он производительность для вас.

ИЗМЕНИТЬ ПРИМЕЧАНИЕ: не использовали WPF.

Ответ 12

Вы говорите, что память растет экспоненциально. Нет, это квадратичный рост, т.е. Рост полинома, который не столь драматичен, как экспоненциальный рост.

Вы создаете строки, содержащие следующее количество элементов:

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 не помогает, так как вы хотите также отображать промежуточные результаты.