2009-06-04 5 views
8

현재 FlowDocument 리소스를 릴리스 할 때 발생하는 문제를 해결하려고합니다. rtf 파일을로드하고 TextRange.Load와 함께 FlowDocument에 넣습니다. 나는이 작업을 수행 한 후 해당 리소스를 보유하고 GC가 수집하지 않는다는 것을 알았습니다. 나는 메모리 프로파일 러를 돌렸고 이것이 사실이라는 것을 알았다. 또한 FlowDocument에 rtf를 실제로로드 할 때까지 좁혔습니다. 그 일을하지 않으면 모든 것이 정상입니다. 그래서 이것이 이것이 문제라는 것을 압니다.C#의 FlowDocument 메모리 문제

이 문제를 어떻게 해결할 수 있는지에 대한 지침이 필요합니다. 다음은 rtf와 모든 것을로드하는 코드입니다. 나는 다른 모든 코드를 주석 처리했으며 GC.Collect()를 시도한 것 외에도 자체 범위에 넣었다. 어떤 도움이라도 대단히 감사합니다.

편집 : 여기에 내 코드가 가득 차 있습니다. 나는 그것을 실행하기 위해 벌거 벗은 본질을 제외하고는 다른 모든 것을 꺼냈다. 문제는 여전히 존재합니다. FlowDocument와 TextRange는 다른 곳에서는 참조되지 않습니다.

public LoadRTFWindow(string file) 
    { 
     InitializeComponent(); 

     using (FileStream reader = new FileStream(file, FileMode.Open)) 
     { 
      FlowDocument doc = new FlowDocument(); 
      TextRange range = new TextRange(doc.ContentStart, doc.ContentEnd); 
      range.Load(reader, System.Windows.DataFormats.Rtf); 
     } 
     GC.Collect(); 
     GC.WaitForPendingFinalizers(); 
     GC.Collect(); 
    } 

는 나는 내 문제를 해결 도움이 될 기대했다 this post를 발견하지만 난 그것으로 운이 없었다. 모든 유형의 도움을 크게 주시면 감사하겠습니다. 고맙습니다.

편집 : 나는 이것이 내가 점검하고있는 주요 방법을 언급해야한다. Windows 작업 관리자가 열려 있고 내 응용 프로그램의 프로세스가 사용하는 메모리 사용량을보고 있습니다. 위의 코드를 실행하면 TextRange.Load() (큰 400 페이지 RTF)를 수행하는 동안 응용 프로그램이 40,000K에서 70,000K로 이동하고 한 번 완료되면 61,000K로 떨어지고 거기에 머물러 있습니다. 내 기대는 그것이 40,000K로 떨어지거나 적어도 그것과 아주 근접 할 것이라는 것이다.

앞에서 언급 한 것처럼 메모리 프로파일 러를 사용하여 많은 양의 단락, 실행 ...이 있음을 확인했습니다. 개체는 여전히 살아 있습니다.

답변

0

FlowDocument의 부모가 걸려 있지 않은지 확인하십시오 (here 참조). "FlowDocument를 인스턴스화하면 자동으로 컨텐트를 호스팅하는 부모 FlowDocumentPageViewer가 생성됩니다." 그 컨트롤이 주위에 걸려 있으면 문제의 원인이 될 수 있습니다.

+0

어디서나 부모를 언급하지 않습니다. 그래서 나는 FlowDocumentPageViewer가 내 문제라고 생각하지 않는다. 나는 원래 게시물에서 코드를 더 작성했다. – Jasson

1

FlowDocument는 System.Windows.Threading.Dispatcher를 사용하여 모든 리소스를 해제합니다. finalizers는 모든 리소스가 해제 될 때까지 현재 스레드를 차단하므로 finalizers는 사용하지 않습니다. 따라서 사용자는 UI가 일시 정지되는 것을 볼 수 있습니다. 디스패처는 백그라운드 스레드에서 실행 중이며 UI에 미치는 영향이 적습니다.
GC.WaitForPendingFinalizers();를 호출합니다. 이것에 영향을 미치지 않습니다. 기다리기 위해 Dispatcher가 작업을 마칠 수 있도록 코드를 추가하기 만하면됩니다. Thread.CurrentThread.Sleep (2000/* 2 초 * /)와 같은 것을 추가하려고 시도하십시오.

편집 : 일부 응용 프로그램의 디버깅 중에이 문제를 발견했습니다. 내가 문제를 재현하기 위해 노력했습니다

static void Main(string[] args) 
    { 
     Console.WriteLine("press enter to start"); 
     Console.ReadLine(); 

     var path = "c:\\1.rtf"; 

     for (var i = 0; i < 20; i++) 
     { 
      using (var stream = new FileStream(path, FileMode.Open)) 
      { 
       var document = new FlowDocument(); 
       var range = new TextRange(document.ContentStart, document.ContentEnd); 

       range.Load(stream, DataFormats.Rtf); 
      } 
     } 

     Console.WriteLine("press enter to run GC."); 
     Console.ReadLine(); 

     GC.Collect(); 
     GC.WaitForPendingFinalizers(); 

     Console.WriteLine("GC has finished ."); 
     Console.ReadLine(); 
    } 

: 나는 (콘솔 프로그램) 다음과 같은 매우 간단한 테스트 케이스를 썼다. 나는 그것을 여러 번 실행했고 완벽하게 작동했습니다. 누수가 없었습니다 (시간당 약 3,2Kb, 핸들 36 개). 나는이 프로그램을 VS에서 디버그 모드로 실행할 때까지 재현 할 수 없었다 (Ctrl + f5 대신 f5 만). 처음에 20,5Kb,로드 후 31,7Kb, GC 이전, GC 후 31Kb를 받았습니다. 결과와 비슷합니다.
따라서 VS 아래가 아닌 릴리스 모드에서이 문제를 재현 해 볼 수 있습니까?당신이 실제 힙 크기를 확인해야합니다, 나는 런타임은 항상 즉시 수집 된 메모리를 해제하지 않습니다 발견했습니다

GC.Collect(); 
GC.WaitForPendingFinalizers(); 
GC.Collect(); 

을 또한 : 자체가 모든 것을 수집하지 않습니다에 의해

+0

그래, 나는 FlowDocument가 System.Windows.Threading.DispatcherObject에서 상속 받았다는 것을 알아 차렸다. 나는 이전에 이와 비슷한 것을 시도해 보았고 이전과 같은 결과를 보았습니다. 그것은 시도가 가치가 있었다 :). Thread.Sleep()은 정적 메서드 btw입니다. – Jasson

+0

아, 죄송합니다, 아주 늦었고 조금 졸 렸습니다. 나는 오늘이 문제를 점검하고 약간을 디버깅하려고 노력할 것이다. 그것은 매우 흥미로워진다. – zihotki

+0

감사합니다. 행운을 빕니다. 나는이 문제에 정말 곤두박질하지만 여전히 해결책을 찾고있다. – Jasson

0

GC.Collect(), 당신은 실행해야 작업 관리자에 의존하는 대신

+0

나는 이것을 시도했는데 실제 힙을 보는 것뿐만 아니라 조금 더 도움이되는 것처럼 보였습니다. 실제 힙은 작업 관리자가 보여주는 것보다 조금 나아졌지만 전체적으로 문제가 남아 있습니다. 나는 그것이 어떤 종류의 메모리 누출인지 꽤 확신한다; 그러나 나는 그것이 정확히 무엇인지 보지 못하고있다. 내가 제공 한 코드는 내 지식에 완전히 포함되어 있기 때문에 필자는 전에 이런 식으로 다루지 않았기 때문에 난처한 상황에 처하게되었다. 다른 제안 사항이 있으십니까? 지금까지 도움을 주셔서 감사합니다. – Jasson

0

해당 파일 핸들을 해제하는 것이 좋습니다. 또한 IDisposable.Dispose (의도 한 말장난 없음) 대신 "using"문을 사용하는 것이 좋습니다.

+0

실제로 코드에서 사용하고 있는데, 왜 내가 거기에 넣지 않았는지 확신 할 수 없습니다. 나는 초 안에 코드를 업데이트 할 것이다. 파일 핸들을 공개한다고 생각할 때 FileStream을 의미합니까? 나는 생각하지 않는다. 나는 네가 말하는 것에 대해 확신하지 못한다고 생각한다. 도와 주셔서 감사합니다. – Jasson

+0

새로 편집 한 코드가 훨씬 깨끗해 보입니다.사실 C# 코드는 JIT로 컴파일되므로 어떤 것을 실행하면 작업 세트가 커집니다. 같은 코드를 반복해서 실행하면 (이미 JIT 된 상태입니다.) 할당 된 메모리가 커집니다. 위의 코드로 메모리 누출이 있다고 가정하지 않습니다. – GregC

+0

나는 이것이 메모리 누출이라고 확신한다. 나는 코드를 가지고 놀아 보았고 RichTextBox가 실제로 텍스트가있는 창에 표시되면 상당한 양의 메모리가 RichTextBox로 이동한다는 것을 알았습니다. 창을 닫을 때이 메모리의 대부분이 복구되지 않는 것 같습니다. RichTextBox 디스플레이를 사용하여, 아직 복구되지 않은 FlowDocument로 들어가는 많은 메모리가 있음을 확인했습니다. 이것은 FlowTocument가 있기 때문에 RichTextBox 문제와 거의 같습니다. 그러나이 FlowDocument의 범위가 매우 까다로워서 나는 왜 우물쭈물합니다. – Jasson

0

문제점을 재현하려고 시도했지만 컴퓨터에서 발생하지 않습니다.

작업 관리자는 작업 집합 크기를 표시합니다.이 크기는 프로그램의 메모리 사용을 정확하게 나타내지 않습니다. 대신 perfmon을 사용해보십시오.

  1. 시작 -> 실행 -> perfmon.msc를
  2. 추가 .NET의 CLR 메모리/응용 프로그램에 대한 모든 힙에서 #의 바이트

이제 실험을 반복하고 카운터가 올라가고 유지하는지 확인 . 그렇지 않으면 관리되는 메모리가 누출되지 않는 것입니다.

+0

perfmon을 사용하여 예제 프로그램에서 처음으로 메모리를 실행할 때 메모리가 복구되지 않는 것처럼 보였다. 그러나 후속 실행은 메모리를 복구하는 것처럼 보입니다. 내 실제 응용 프로그램에서 메모리가 전혀 복구되지 않습니다. 두 번째 창에서이 코드를 실행하고 있습니다. 아마도 그 이유가 무엇일까요? 그런데 처음으로 메모리를 복구하는 이유는 무엇입니까? 또한 WPF MSDN 포럼에서이 질문을했습니다. http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/15e2b42e-1975-4d68-8ddb-59bbcd1f0633/ – Jasson

+0

둘째 창에 대한 참조가 있습니까? 첫 번째 창에서 두 번째 창의 모든 구성 요소에 의해 노출 된 이벤트를 구독 했습니까? –

6

메모리 누수가 있음을 확인한 경우 다음과 같이 문제를 디버깅 할 수 있습니다.

  1. 설치 디렉토리에서 http://www.microsoft.com/whdc/devtools/debugging/installx86.mspx#a
  2. 화재 최대가 Windbg에서 Windows 용 디버깅 도구 설치합니다.
  3. 응용 프로그램을 시작하고 메모리 누수 작업을 수행하십시오.
  4. 응용 프로그램에 Windbg을 첨부하십시오 (F6).
  5. 유형 .loadby sos mscorwks
  6. 유형 !dumpheap -type FlowDocument
  7. 확인 위의 명령의 결과. 당신이 (주소를 포함) 첫 번째 열의 각 값에 대해 여러 FlowDocuments를 참조 할 경우

유형 !gcroot <value of first column> 기준에 들고있어 당신을 표시해야합니다

.

+1

.net 4.0+를 사용하는 경우, 5 단계를'.loadby sos clr' – maxp

1

나는 이전에 # 7을 끝내었다. Windbg을 처음 사용했기 때문에 주소를 찾기 위해 무엇을해야할지 몰랐습니다. 여기에 내가 가진 것이있다.

Address  MT  Size 
0131c9c0 55cd21d8  84  
013479e0 55cd21d8  84  
044dabe0 55cd21d8  84  
total 3 objects 
Statistics: 
     MT Count TotalSize Class Name 
55cd21d8  3   252 System.Windows.Documents.FlowDocument 
Total 3 objects 
0:011> !gcroot 0131c9c0 
Note: Roots found on stacks may be false positives. Run "!help gcroot" for 
more info. 
Scan Thread 0 OSTHread 47c 
Scan Thread 2 OSTHread be8 
Scan Thread 4 OSTHread 498 
DOMAIN(001657B0):HANDLE(WeakSh):911788:Root:0131ff98(System.EventHandler)-> 
0131fcd4(System.Windows.Documents.AdornerLayer)-> 
012fad68(MemoryTesting.Window2)-> 
0131c9c0(System.Windows.Documents.FlowDocument) 
DOMAIN(001657B0):HANDLE(WeakSh):911cb0:Root:0131ca90(MS.Internal.PtsHost.PtsContext)-> 
0131cb14(MS.Internal.PtsHost.PtsContext+HandleIndex[])-> 
0133d668(MS.Internal.PtsHost.TextParagraph)-> 
0131c9c0(System.Windows.Documents.FlowDocument) 
DOMAIN(001657B0):HANDLE(WeakSh):9124a8:Root:01320a2c(MS.Internal.PtsHost.FlowDocumentPage)-> 
0133d5d0(System.Windows.Documents.TextPointer)-> 
0131ca14(System.Windows.Documents.TextContainer)-> 
0131c9c0(System.Windows.Documents.FlowDocument) 

(읽기 쉽도록 코드 블록에 넣었습니다). 이것은 창을 닫은 후입니다. 그래서 그것은 그것이 몇 가지로 참조되고있는 것처럼 보입니다. 이제는이 참조를 해제하여 FlowDocument를 릴리스 할 수있는 방법에 대해 알고 있습니다.

도움 주셔서 감사합니다. 나는 마침내 어떤 근거를 얻고있는 것처럼 느낍니다.

3

우리는 다른 스레드에서 플로우 문서를 생성하는 것과 비슷한 문제가있었습니다. 메모리 프로파일 러에서 객체가 여전히 존재한다는 것을 알았습니다.

지금까지 내가 아는 한,에 설명 된대로이 link

"는 FlowDocument가 생성 될 때, 상대적으로 고가의 서식 상황에 맞는 개체가 또한 StructuralCache에서 생성됩니다. 당신이 꽉 루프에서 여러 FlowDocs을 만들 때, StructuralCache가 각 FlowDoc에 대해 만들어집니다. 루프의 마지막 부분에서 Gc.Collect를 호출하여 메모리를 복구 해 봅시다. StructuralCache에서 finalizer가이 형식 컨텍스트를 릴리스하지만 즉시 실행하지는 않습니다. 종료자는 효과적으로 컨텍스트를 릴리스하는 작업을 예약합니다. DispatcherPriority.Background. "

Dispatcher 작업이 완료 될 때까지 Flow 문서가 메모리에 저장됩니다. 그래서 디스패처 작업을 완료하는 것이 좋습니다.

당신은 Dispatcher가 현재 SystemIdle가 가장 낮은 우선 순위로,이 완료 될 때까지 큐에 모든 작업을 강제로, 아래의 코드를 시도 후 실행되는 스레드에있는 경우 : 당신이 경우

Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.SystemIdle, 
    new DispatcherOperationCallback(delegate { return null; }), null); 

단 하나의 흐름 문서가 스레드에서 만든, 그래서 같은 것을 시도했다 나의 경우처럼 Dispatcher가 실행되지 않는 스레드에서 :이 큐에

var dispatcher = Dispatcher.CurrentDispatcher; 
dispatcher.BeginInvokeShutDown(DispatcherPriority.SystemIdle) 
Dispatcher.Run(); 

을 매우 끝에서 종료 한 후 실행됩니다 FlowDocument를 지우고 끝내는 디스패처 운영자가 종료됩니다.