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

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

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

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

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

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

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

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

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

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

Ответы [ 12 ]

119 голосов
/ 05 января 2012

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

14 голосов
/ 10 января 2012

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

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

9 голосов
/ 05 января 2012

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

5 голосов
/ 05 января 2012

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

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

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

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

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

3 голосов
/ 05 января 2012

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

Но главной проблемой, похоже, являются требования, показывающие, что 180 000 предметов не могут быть нацелены на пользователя (человека), равно как и не изменяются в «реальном времени».

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

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

2 голосов
/ 05 января 2012

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

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

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

2 голосов
/ 05 января 2012

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

1 голос
/ 05 января 2012

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

1 голос
/ 05 января 2012

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, чтобы оставаться в пределах 2 ГБ.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...