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


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

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

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

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

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


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

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

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

Если Я прокомментируйте обновление текстового поля результатов, память остается относительно неподвижной примерно на 90K, поэтому я могу предположить, что запись ResultsText.Text += someValue это то, что заставляет его есть память.

мой вопрос, почему это? Что является лучшим способом добавления данных в текстовое поле.Текст, который не съедает память?

мой код выглядит так:

try
{
    report.SetParameterValue("Id", id);

    report.ExportToDisk(ExportFormatType.PortableDocFormat,
        string.Format(@"{0}{1}.pdf", new object[] { outputLocation, id}));

    // ResultsText.Text += string.Format("Exported {0}rn", id);
}
catch (Exception ex)
{
    ErrorsText.Text += string.Format("Failed to export {0}: {1}rn", 
        new object[] { id, ex.Message });
}

также стоит упомянуть, что приложение является одноразовым, и не имеет значения, что оно займет несколько часы (или дни :)) для создания всех отчетов. Моя главная проблема заключается в том, что если он достигнет предела системной памяти, он перестанет работать.

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

12 81

12 ответов:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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 ГБ.

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

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

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

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

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

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

изменить примечание: не использовали WPF.

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

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

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 как хорошо!

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