Короткий вопрос
У меня есть цикл, который выполняется 180 000 раз. В конце каждой итерации предполагается добавлять результаты в TextBox, который обновляется в реальном времени.
Использование MyTextBox.Text += someValue
приводит к тому, что приложение съедает огромное количество памяти, и у него заканчивается доступная память после нескольких тысяч записей.
Есть ли более эффективный способ добавления текста до TextBox.Text
180 000 раз?
Изменить Меня действительно не волнует результат этого конкретного случая, однако я хочу знать, почему это похоже на потребление памяти, и есть ли более эффективный способ добавления текста в TextBox.
Длинный (оригинальный) вопрос
У меня есть небольшое приложение, которое считывает список идентификационных номеров в файле CSV и генерирует отчет в формате PDF для каждого из них. После создания каждого файла PDF к нему ResultsTextBox.Text
добавляется идентификационный номер отчета, который был обработан и был успешно обработан. Процесс выполняется в фоновом потоке, поэтому ResultsTextBox обновляется в реальном времени по мере обработки элементов.
В настоящее время я использую приложение для 180000 идентификационных номеров, однако объем памяти, который занимает приложение, со временем растет экспоненциально. Он начинается примерно с 90 КБ, но примерно на 3000 записей он занимает примерно 250 МБ, а на 4000 записей приложение занимает около 500 МБ памяти.
Если я закомментирую обновление текстового поля результатов, память останется относительно постоянной на уровне примерно 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 });
}
Следует также отметить, что приложение является одноразовым, и не имеет значения, что на создание всех отчетов уйдет несколько часов (или дней :)). Меня больше всего беспокоит то, что если он достигнет предела системной памяти, он перестанет работать.
Я согласен оставить строку, обновляющую текстовое поле результатов, закомментированную, чтобы запустить эту вещь, но я хотел бы знать, есть ли более эффективный способ добавления данных в a TextBox.Text
для будущих проектов.
StringBuilder
для добавления текста, а затем, по завершении, присвоитьStringBuilder
значение текстовому полю.Ответы:
Я подозреваю, что использование памяти настолько велико, потому что текстовые поля поддерживают стек, чтобы пользователь мог отменить / повторить текст. В вашем случае эта функция не требуется, поэтому попробуйте установить
IsUndoEnabled
значение false.источник
UndoLimit
реалистичное значение. Значение по умолчанию -1 указывает на неограниченный стек. Ноль (0) также отключит отмену.Используйте
TextBox.AppendText(someValue)
вместоTextBox.Text += someValue
. Его легко пропустить, поскольку он находится в TextBox, а не в TextBox.Text. Как и StringBuilder, это позволит избежать создания копий всего текста каждый раз, когда вы что-то добавляете.Было бы интересно посмотреть, как это соотносится с
IsUndoEnabled
флагом из ответа keyboardP.источник
bool CanUndo
свойствоНе добавляйте непосредственно к свойству текста. Используйте StringBuilder для добавления, затем, когда закончите, установите .text в готовую строку из построителя строк
источник
Вместо текстового поля я бы сделал следующее:
источник
Лично я всегда использую
string.Concat
*. Я помню, как много лет назад читал здесь вопрос о переполнении стека, в котором была статистика профилирования, сравнивающая часто используемые методы, и (похоже) вспомнил, что этоstring.Concat
победило.Тем не менее, лучшее, что я могу найти, - это этот справочный вопрос и этот конкретный вопрос
String.Format
vs.StringBuilder
, в котором упоминается, что внутреннеString.Format
используетсяStringBuilder
. Это заставляет меня задуматься, а не где-нибудь твоя память.** на основе комментария Джеймса, я должен упомянуть, что я никогда не использую тяжелое форматирование строк, так как я сосредоточен на веб-разработке. *
источник
string.Format
/StringBuilder
?Может быть, текстовое поле пересмотреть? ListBox, содержащий строковые элементы, вероятно, будет работать лучше.
Но основная проблема, похоже, заключается в требованиях. Отображение 180 000 элементов не может быть нацелено на пользователя (человека), равно как и не может быть изменено в «Реальном времени».
Предпочтительным способом было бы показать образец данных или индикатор прогресса.
Если вы хотите сбросить его на бедного пользователя, выполните пакетное обновление строки. Ни один пользователь не мог заметить более 2 или 3 изменений в секунду. Так что если вы производите 100 секунд в секунду, делайте группы по 50 штук.
источник
В некоторых ответах это упоминалось, но никто не сказал об этом прямо, что удивительно. Строки неизменяемы, что означает, что строка не может быть изменена после ее создания. Следовательно, каждый раз, когда вы присоединяетесь к существующей String, необходимо создавать новый объект String. Очевидно, также необходимо создать память, связанную с этим строковым объектом, что может стать дорогостоящим по мере того, как ваши строки становятся все больше и больше. В колледже я однажды совершил любительскую ошибку, объединив строки в программе Java, которая выполняла сжатие кода Хаффмана. Когда вы объединяете очень большие объемы текста, объединение строк может действительно навредить вам, если вы могли бы просто использовать StringBuilder, как некоторые здесь упоминали.
источник
Используйте StringBuilder, как было предложено. Попробуйте оценить окончательный размер строки, а затем используйте это число при создании экземпляра StringBuilder. StringBuilder sb = новый StringBuilder (estSize);
При обновлении TextBox просто используйте присваивание, например: textbox.text = sb.ToString ();
Следите за межпотоковыми операциями, как указано выше. Однако используйте BeginInvoke. Нет необходимости блокировать фоновый поток во время обновления пользовательского интерфейса.
источник
A) Intro: уже упоминалось, используйте
StringBuilder
Б) Пункт: не обновляйте слишком часто, т.е.
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
inViewModel
позволит избежать беспорядка при повторных привязках строк и привяжет их кMyTextBox.Text
. Этот сценарий многократно увеличит производительность и снизит использование памяти.источник
Что не было упомянуто, так это то, что даже если вы выполняете операцию в фоновом потоке, обновление самого элемента пользовательского интерфейса ДОЛЖНО произойти в самом основном потоке (в любом случае в WinForms).
При обновлении текстового поля у вас есть код, который выглядит как
if(textbox.dispatcher.checkAccess()){ textbox.text += "whatever"; }else{ textbox.dispatcher.invoke(...); }
Если это так, то ваша фоновая операция определенно ограничена обновлением пользовательского интерфейса.
Я бы посоветовал, чтобы ваш фоновый оператор использовал 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
тоже !Как предлагали другие, используйте вместо этого ListBox. Здесь вы можете добавлять новые строки, не создавая огромных строк. A
StringBuild
не помогает, поскольку вы также хотите отображать промежуточные результаты.источник