2008-10-20 9 views
5

Der folgende Codeausschnitt veranschaulicht einen Speicherverlust beim Öffnen von XPS-Dateien. Wenn Sie es ausführen und den Task-Manager beobachten, wird dieser größer und gibt keinen Speicher frei, bis die App beendet wird.Öffnen von XPS-Dokument in .Net verursacht einen Speicherverlust

'****** Konsolenanwendung BEGINNT.

Module Main 

    Const DefaultTestFilePath As String = "D:\Test.xps" 
    Const DefaultLoopRuns As Integer = 1000 

    Public Sub Main(ByVal Args As String()) 
     Dim PathToTestXps As String = DefaultTestFilePath 
     Dim NumberOfLoops As Integer = DefaultLoopRuns 

     If (Args.Count >= 1) Then PathToTestXps = Args(0) 
     If (Args.Count >= 2) Then NumberOfLoops = CInt(Args(1)) 

     Console.Clear() 
     Console.WriteLine("Start - {0}", GC.GetTotalMemory(True)) 
     For LoopCount As Integer = 1 To NumberOfLoops 

      Console.CursorLeft = 0 
      Console.Write("Loop {0:d5}", LoopCount) 

      ' The more complex the XPS document and the more loops, the more memory is lost. 
      Using XPSItem As New Windows.Xps.Packaging.XpsDocument(PathToTestXps, System.IO.FileAccess.Read) 
       Dim FixedDocSequence As Windows.Documents.FixedDocumentSequence 

       ' This line leaks a chunk of memory each time, when commented out it does not. 
       FixedDocSequence = XPSItem.GetFixedDocumentSequence 
      End Using 
     Next 
     Console.WriteLine() 
     GC.Collect() ' This line has no effect, I think the memory that has leaked is unmanaged (C++ XPS internals). 
     Console.WriteLine("Complete - {0}", GC.GetTotalMemory(True)) 

     Console.WriteLine("Loop complete but memory not released, will release when app exits (press a key to exit).") 
     Console.ReadKey() 

    End Sub 

End Module 

'****** Konsolenanwendung ENDE.

Der Grund dafür, dass es tausend Mal wiederholt wird, liegt darin, dass mein Code viele Dateien verarbeitet und Speicher schnell verliert, wodurch eine OutOfMemoryException erzwungen wird. Das Erzwingen der Speicherbereinigung funktioniert nicht (ich vermute, dass es sich um einen nicht verwalteten Speicherbereich in den XPS-internen Komponenten handelt).

Der Code war ursprünglich in einem anderen Thread und einer anderen Klasse, wurde jedoch vereinfacht.

Jede Hilfe sehr geschätzt.

Ryan

Antwort

6

Nun, ich habe es gefunden. Es ist ein Fehler im Framework, und um es zu umgehen, fügen Sie einen Aufruf von UpdateLayout hinzu. Die Verwendung der Anweisung kann folgendermaßen geändert werden, um eine Korrektur bereitzustellen:

 Using XPSItem As New Windows.Xps.Packaging.XpsDocument(PathToTestXps, System.IO.FileAccess.Read) 
      Dim FixedDocSequence As Windows.Documents.FixedDocumentSequence 
      Dim DocPager As Windows.Documents.DocumentPaginator 

      FixedDocSequence = XPSItem.GetFixedDocumentSequence 
      DocPager = FixedDocSequence.DocumentPaginator 
      DocPager.ComputePageCount() 

      ' This is the fix, each page must be laid out otherwise resources are never released.' 
      For PageIndex As Integer = 0 To DocPager.PageCount - 1 
       DirectCast(DocPager.GetPage(PageIndex).Visual, Windows.Documents.FixedPage).UpdateLayout() 
      Next 
      FixedDocSequence = Nothing 
     End Using 
+0

Jeder Beweis, dass dies ein echter Fehler ist? Wird es zusammen mit Repro-Schritten gemeldet? – Will

+0

Der Beweis ist im Pudding, wie sie, laufen Sie und sehen Sie (kommentieren Sie das DirectCast oben, Sie werden ein Leck bekommen). Ich habe es nicht mit MS Connect angesprochen, aber mit MS Social (http://social.msdn.microsoft.com/Forums/en-US/windowsxps/thread/e31edefa-b07e-450d-8ab8-5a171ee8d4c1/?ppud= 4). –

+0

Hatte das gleiche Problem heute. Danke, das hat es behoben! Übrigens, wie haben Sie diese Problemumgehung gefunden? Einfach herumprobieren? –

0

Ich kann Ihnen keine autoritativen Ratschläge geben, aber ich habe ein paar Gedanken haben:

  • Wenn Sie Ihr Gedächtnis in der Schleife zu sehen, müssen Sie Speicher zu sammeln auch in der Schleife. Sonst werden Sie erscheinen, um Speicher durch Design zu verlieren, da es effizienter ist, größere Blöcke weniger häufig (nach Bedarf) zu sammeln, anstatt ständig kleine Mengen zu sammeln. In diesem Fall sollte der Scope-Block, der die using-Anweisung erstellt, ausreichen, aber Ihre Verwendung von GC.Collect weist darauf hin, dass möglicherweise etwas anderes vor sich geht.
  • Sogar GC.Collect ist nur ein Vorschlag (okay, sehr stark Vorschlag, aber immer noch ein Vorschlag): es garantiert nicht, dass alle ausstehenden Speicher gesammelt wird.
  • Wenn der interne XPS-Code tatsächlich Speicherlecks verursacht, besteht die einzige Möglichkeit, das Betriebssystem zu erzwingen, darin, das Betriebssystem zu überlisten, dass die Anwendung beendet ist. Um das zu tun, könnten Sie vielleicht eine Dummy-Anwendung erstellen, die Ihren xps-Code verarbeitet und aus der Haupt-App aufgerufen wird, oder Sie können den xps-Code in die eigene AppDomain innerhalb Ihres Hauptcodes verschieben.
4

Ist heute drin. Interessanterweise fand ich, als ich mit Reflector.NET in die Dinge schaute, den Fix, der das Aufrufen von UpdateLayout() für den ContextLayoutManager erforderte, der dem aktuellen Dispatcher zugeordnet ist. (lese: keine Notwendigkeit, über Seiten zu iterieren).

Grundsätzlich ist der Code aufgerufen werden (Verwendung Reflexion hier) ist:

ContextLayoutManager.From(Dispatcher.CurrentDispatcher).UpdateLayout(); 

Auf jeden Fall fühlt sich wie ein kleines Versehen von MS.

Für den faulen oder nicht vertraut, dieser Code funktioniert:

Assembly presentationCoreAssembly = Assembly.GetAssembly(typeof (System.Windows.UIElement)); 
Type contextLayoutManagerType = presentationCoreAssembly.GetType("System.Windows.ContextLayoutManager"); 
object contextLayoutManager = contextLayoutManagerType.InvokeMember("From", 
BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.NonPublic, null, null, new[] {dispatcher}); 
contextLayoutManagerType.InvokeMember("UpdateLayout", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, contextLayoutManager, null); 

FxCop wird sich beschweren, aber vielleicht ist es im nächsten Rahmen Version behoben.Der Code des Autors scheint "sicherer" zu sein, wenn Sie keine Reflektion verwenden möchten.

HTH!

0

Hinzufügen UpdateLayout kann das Problem nicht lösen. Nach http://support.microsoft.com/kb/942443 "Preload die PresentationCore.dll-Datei oder die PresentationFramework.dll-Datei in der primären Anwendungsdomäne" wird benötigt.

+2

Ähm, es hat das Problem gelöst, obwohl es nicht die beste Lösung wäre. –

0

Interessant. Das Problem ist immer noch in .net Framework 4.0 vorhanden. Mein Code ging wild durchsickern.

Der vorgeschlagene Fix - wo UpdateLayout in einer Schleife direkt nach dem Erstellen der FixedDocumentSequence aufgerufen wird, behebt das Problem für mich nicht auf einem 400 Seiten Testdokument.

Die folgende Lösung DID behebt das Problem für mich. Wie in früheren Fixes habe ich den Aufruf von GetFixedDocumentSequence() außerhalb der Schleife für jede Seite verschoben. Die "using" Klausel ... faire Warnung, dass ich immer noch nicht sicher bin, ob es korrekt ist. Aber es tut nicht weh. Das Dokument wird anschließend erneut zum Generieren von Seitenvorschauen auf dem Bildschirm verwendet. Es scheint also nicht weh zu tun.

DocumentPaginator paginator 
    = document.GetFixedDocumentSequence().DocumentPaginator; 
int numberOfPages = paginator.ComputePageCount(); 


for (int i = 0; i < NumberOfPages; ++i) 
{ 
    DocumentPage docPage = paginator.GetPage(nPage); 
    using (docPage) // using is *probably* correct. 
    { 
     // VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV 

     ((FixedPage)(docPage.Visual)).UpdateLayout(); 

     // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
     // Adding THAT line cured my leak. 

     RenderTargetBitmap bitmap = GetXpsPageAsBitmap(docPage, dpi); 

     .... etc... 
    } 

} 

In Wirklichkeit geht die Update Linie in meiner GetXpsPageAsBitmap Routine (ommited für Klarheit), die bis dahin erbringen Code ziemlich identisch ist.

Danke an alle, die dazu beigetragen haben.