루프 중에 TextBox.Text에 추가하면 반복 할 때마다 더 많은 메모리를 차지하는 이유는 무엇입니까?


82

짧은 질문

180,000 번 실행되는 루프가 있습니다. 각 반복이 끝날 때 실시간으로 업데이트되는 TextBox에 결과를 추가해야합니다.

를 사용 MyTextBox.Text += someValue하면 응용 프로그램이 엄청난 양의 메모리를 사용하게되며 수천 개의 레코드를 사용하면 사용 가능한 메모리가 부족합니다.

TextBox.Text180,000 번에 텍스트를 추가하는 더 효율적인 방법이 있습니까?

편집 나는이 특정 경우의 결과에 대해 정말로 신경 쓰지 않지만 이것이 메모리 돼지 인 것처럼 보이는 이유와 TextBox에 텍스트를 추가하는 더 효율적인 방법이 있는지 알고 싶습니다.


긴 (원본) 질문

CSV 파일의 ID 번호 목록을 읽고 각각에 대한 PDF 보고서를 생성하는 작은 앱이 있습니다. 각 pdf 파일이 생성 된 후에 ResultsTextBox.Text는 처리되고 성공적으로 처리 된 보고서의 ID 번호가 추가됩니다. 프로세스는 백그라운드 스레드에서 실행되므로 ResultsTextBox는 항목이 처리 될 때 실시간으로 업데이트됩니다.

현재 180,000 개의 ID 번호에 대해 앱을 실행하고 있지만 시간이 지남에 따라 애플리케이션이 차지하는 메모리는 기하 급수적으로 증가하고 있습니다. 약 90K에서 시작하지만 약 3000 개 레코드가 약 250MB를 차지하고 4000 개 레코드가 애플리케이션이 약 500MB의 메모리를 차지합니다.

Results 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향후 프로젝트를 위해에 데이터를 추가하는 메모리 효율적인 방법이 있는지 알고 싶습니다 .


7
a StringBuilder를 사용 하여 텍스트를 추가 한 다음 완료되면 StringBuilder텍스트 상자에 값을 할당 할 수 있습니다.
keyboardP

1
새 Id-s를 추가하는 StringBuilder가 있고 문자열 작성기의 새 값으로 업데이트되는 속성을 사용하고 이것을 텍스트 상자에 바인딩하면 어떻게 될지 모르겠습니다. 특성.
BigL

2
string.Format을 호출 할 때 객체 배열을 초기화하는 이유는 무엇입니까? 2 개의 매개 변수를 사용하는 오버로드가 있으므로 배열 생성을 피할 수 있습니다. 또한 params 오버로드를 사용하면 배후에서 배열이 생성됩니다.
ChaosPandion 2012 년

1
문자열 연결이 반드시 비효율적 인 것은 아닙니다. 여러 작업 단위에서 문자열을 연결하고 각 작업 단위 사이에 결과를 표시하는 경우 StringBuilder보다 효율적입니다. StringBuilder는 루프를 통해 문자열을 빌드 한 다음 루프 끝에 결과를 기록 할 때만 실제로 더 효율적입니다.
James Michael Hare

3
나는 그 :-) 꽤 인상적 기계의 말을하려고했다
제임스 마이클 헤어

답변:


119

메모리 사용량이 너무 큰 이유는 텍스트 상자가 스택을 유지하여 사용자가 텍스트를 실행 취소 / 다시 실행할 수 있기 때문이라고 생각합니다. 해당 기능은 귀하의 경우에 필요하지 않은 것 같으므로 IsUndoEnabledfalse로 설정하십시오 .


1
MSDN 링크에서 : "메모리 누수 코드에서 값을 자주 설정하여 응용 프로그램에서 메모리가 증가하는 경우 텍스트 블록의 실행 취소 스택이 메모리의"누수 "가 될 수 있습니다.이 속성을 사용하여 비활성화 할 수 있습니다. 그리고 메모리 누수로가는 길을 정리합니다. "
웨이브

33
대부분의 경우 사용자와 개발자는 텍스트 상자가 표준 텍스트 상자처럼 작동하기를 기대합니다 (즉, 실행 취소 / 다시 실행 기능 포함). OP의 요구 사항과 같은 엣지 케이스에서는 장애가 될 수 있습니다. 대다수의 사람들이 사용한다면 기본값이어야합니다. 엣지 케이스에서 표준 기능이 옵트 인되도록 강제하는 이유는 무엇입니까?
keyboardP

1
또는을 UndoLimit실제 값으로 설정할 수도 있습니다 . 기본값 -1은 무제한 스택을 나타냅니다. 제로 (0)도 실행 취소를 비활성화합니다.
myermian

14

사용 TextBox.AppendText(someValue)대신에 TextBox.Text += someValue. TextBox.Text가 아닌 TextBox에 있기 때문에 놓치기 쉽습니다. StringBuilder와 마찬가지로 이것은 무언가를 추가 할 때마다 전체 텍스트의 복사본을 만드는 것을 방지합니다.

이것이 IsUndoEnabledkeyboardP의 답변 의 플래그와 어떻게 비교되는지 보는 것은 흥미로울 것입니다.


윈도우 형태 TextBox.IsUndoEnabled하지 않는 한 윈도우 형태의 경우, 이것은 최선의 솔루션입니다
BrDaHa

Win 양식에는 bool CanUndo속성이 있습니다
imlokesh 2015

9

text 속성에 직접 추가하지 마십시오. 추가를 위해 StringBuilder를 사용하고 완료되면 .text를 stringbuilder의 완성 된 문자열로 설정합니다.


2
루프가 백그라운드 스레드에서 실행되고 결과가 실시간으로 업데이트된다는 것을 언급하는 것을 잊었습니다
Rachel

5

텍스트 상자를 사용하는 대신 다음을 수행합니다.

  1. 만일을 대비하여 텍스트 파일을 열고 오류를 로그 파일로 스트리밍하십시오.
  2. 목록 상자 컨트롤을 사용하여 오류를 표시하여 잠재적으로 대량의 문자열을 복사하지 않도록합니다.

4

개인적으로 저는 항상 string.Concat*를 사용 합니다. 나는 일반적으로 사용되는 방법을 비교하는 프로파일 링 통계를 가지고있는 Stack Overflow에서 몇 년 전에 여기에서 질문을 읽은 것을 기억 string.Concat합니다.

그럼에도 불구하고 내가 찾을 수있는 최선은 이 참조 질문 과 내부적으로 를 사용하는 것을 언급하는 이 특정 String.FormatStringBuilder 질문 입니다. 이것은 당신의 기억력이 다른 곳에 있는지 궁금합니다.String.FormatStringBuilder

** James의 의견에 따르면 웹 기반 개발에 중점을두기 때문에 무거운 문자열 형식을 지정하지 않는다는 점을 언급해야합니다. *


나는 동의한다. 때때로 사람들은 "항상 X를 사용한다. X가 가장 좋다"고 말하는 틀에 갇힌다. 이것은 보통 지나치게 단순화 된 것이다. string.Concat (), string.Format () 및 StringBuilder 사이에는 많은 미묘한 차이가 있습니다. 내 경험 법칙은 각각의 용도를 사용하는 것입니다 (어리석게 들리지만 사실입니다). 문자열을 결합 할 때 (그리고 즉시 결과를 사용할 때) concat을 사용하고, 사소하지 않은 문자열 형식 (패딩, 숫자 형식 등)을 수행 할 때 Format을 사용하고, 루프 중에 문자열을 작성하는 데 StringBuilder를 사용합니다. 루프의 끝에서 사용됩니다.
James Michael Hare 2012 년

@JamesMichaelHare, 나에게 의미가 있습니다. 여기서 string.Format/ 사용 StringBuilder이 더 적절하다고 제안하고 있습니까?
jwheron

오, 나는 concat이 일반적으로 간단한 문자열 연결에 가장 적합하다는 일반적인 요점에 동의했습니다. "경험의 법칙"의 문제는 BCL이 변경되면 .NET 버전에서 버전으로 변경할 수 있다는 것입니다. 따라서 논리적으로 올바른 구문을 고수하는 것이 유지 관리가 더 쉽고 일반적으로 작업 성능이 더 좋습니다. 저는 실제로 이전 블로그 게시물을 가지고 있는데 여기서 세 가지를 비교했습니다. geekswithblogs.net/BlackRabbitCoder/archive/2010/05/10/…
James Michael Hare

정당하게 언급하고 (확실히 확인하고 싶음) "항상"이라는 단어를 사용할 수 있도록 편집 된 답변입니다.
jwheron

3

TextBox를 재고 할 수 있습니까? String Items를 보유한 ListBox는 아마도 더 잘 수행 될 것입니다.

그러나 주된 문제는 요구 사항 인 것 같습니다. 180,000 개의 항목을 표시하는 것은 (인간) 사용자를 겨냥 할 수 없으며 "실시간"에서 변경하는 것도 아닙니다.

바람직한 방법은 데이터 샘플 또는 진행률 표시기를 표시하는 것입니다.

가난한 사용자에게 덤프하고 싶을 때 배치 문자열이 업데이트됩니다. 어떤 사용자도 초당 2 ~ 3 개 이상의 변경 내용을 설명 할 수 없습니다. 따라서 초당 100 개를 생산한다면 50 개 그룹을 만드십시오.


감사합니다 Henk. 일회성이라 글을 쓸 때 게으르다. 상태를 알기 위해 일종의 시각적 출력을 원했고 텍스트 선택 기능과 ScrollBar를 원했습니다. ScrollViewer / Label을 사용할 수 있다고 생각하지만 TextBox에는 ScrollBarrs가 내장되어 있습니다. 문제가 발생할 것으로 예상하지 못했습니다. :)
Rachel

2

일부 응답은 이에 대해 암시했지만 아무도 놀라움을 표명하지 않았습니다. 문자열은 변경 불가능합니다. 즉, 생성 된 후에는 문자열을 수정할 수 없습니다. 따라서 기존 문자열에 연결할 때마다 새 문자열 개체를 만들어야합니다. 해당 문자열 개체와 관련된 메모리도 분명히 만들어야하는데, 문자열이 점점 커질수록 비용이 많이들 수 있습니다. 대학에서 저는 한때 Huffman 코딩 압축을 수행하는 Java 프로그램에서 문자열을 연결하는 아마추어 실수를 저질렀습니다. 매우 많은 양의 텍스트를 연결하는 경우 여기에 언급 된 일부에서 언급했듯이 단순히 StringBuilder를 사용할 수 있었을 때 문자열 연결이 실제로 당신을 해칠 수 있습니다.


2

제안 된대로 StringBuilder를 사용하십시오. 최종 문자열 크기를 추정 한 다음 StringBuilder를 인스턴스화 할 때 해당 숫자를 사용하십시오. StringBuilder sb = new StringBuilder (estSize);

TextBox를 업데이트 할 때는 할당 만 사용하십시오. 예 : textbox.text = sb.ToString ();

위와 같이 크로스 스레드 작업을 확인하십시오. 그러나 BeginInvoke를 사용하십시오. UI가 업데이트되는 동안 백그라운드 스레드를 차단할 필요가 없습니다.


1

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 제한을 유지하십시오.


1

StringBuilderin ViewModel은 문자열 리 바인딩이 엉망이되는 것을 방지하고 MyTextBox.Text. 이 시나리오는 성능을 여러 번 향상시키고 메모리 사용량을 줄입니다.


0

언급되지 않은 것은 백그라운드 스레드에서 작업을 수행하더라도 UI 요소 자체의 업데이트가 메인 스레드 자체 (어쨌든 WinForms에서)에서 발생한다는 것입니다.

텍스트 상자를 업데이트 할 때 다음과 같은 코드가 있습니까?

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

그렇다면 백그라운드 작업이 UI 업데이트로 인해 병목 현상이 발생한 것입니다.

위에서 언급 한대로 백그라운드 작업에서 StringBuilder를 사용하는 것이 좋지만주기마다 텍스트 상자를 업데이트하는 대신 정기적으로 업데이트하여 성능이 향상되는지 확인하십시오.

참고 편집 : WPF를 사용하지 않았습니다.


0

당신은 기억이 기하 급수적으로 증가한다고 말합니다. 아니요, 2 차 성장입니다. . 즉, 다항식 성장으로, 지수 성장만큼 극적이지는 않습니다.

다음 개수의 항목을 포함하는 문자열을 생성합니다.

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중간 결과도 표시하려고하므로 A 는 도움이되지 않습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.