Frage

Das folgende Codefragment veranschaulicht ein Speicherleck, wenn XPS-Dateien zu öffnen. Wenn Sie es ausführen und den Task-Manager zu sehen, wird es wachsen und nicht gespeichert, bis die App Ausgänge freigeben.

'****** 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 ENDS.

Der Grund, es Schleifen tausendmal ist, weil mein Code viele Dateien verarbeitet und Lecks schnell Speicher einen OutOfMemoryException zwingen. Garbage Collection Erzwingen nicht funktioniert (ich vermute, es ist ein unmanaged Teil des Speichers in den XPS-Einbauten).

Der Code war ursprünglich in einem anderen Thread und Klasse, sondern hat dies vereinfacht.

Jede Hilfe sehr geschätzt.

Ryan

War es hilfreich?

Lösung

Nun, ich fand es. Es ist ein Fehler im Rahmen und arbeiten, um es Ihnen einen Anruf zu UpdateLayout hinzufügen. Mit Anweisung kann in die folgenden geändert werden, um eine Lösung zu liefern;

        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

Andere Tipps

Ran in diese heute. Interessant ist, wenn ich in den Dinge betrachtete mit Reflector.NET, fand ich das Update beteiligt Aufruf UpdateLayout () auf dem ContextLayoutManager mit dem aktuellen Dispatcher zugeordnet ist. (Sprich: keine Notwendigkeit, über Seiten zu durchlaufen).

Im Grunde genommen, um der Code (Verwendung Reflexion hier) genannt wird:

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

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

Für die faul 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 geschrieben scheint „sicherer“ zu sein, wenn Sie Reflexion nicht zu verwenden, würde es vorziehen.

HTH!

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

  • Wenn Sie Ihr Gedächtnis in der Schleife sehen möchten, müssen Sie auch Speicher innerhalb der Schleife zu sammeln. Andernfalls werden Sie erscheinen Speicher nach Design auslaufen, da es effizienter ist größere Blöcke zu sammeln weniger häufig (bei Bedarf), anstatt ständig kleine Mengen werden zu sammeln. In diesem Fall wird die Schaffung der Umfang Block die Anweisung using sollte genug, aber die Nutzung von GC.Collect zeigt an, dass vielleicht etwas anderes los ist.
  • Auch GC.Collect ist nur ein Vorschlag (okay, sehr strong Vorschlag, aber immer noch einen Vorschlag): Es ist nicht garantiert, dass alle ausstehenden Speicher gesammelt
  • .
  • Wenn der interne XPS-Code wirklich Speicher undicht ist, die einzige Möglichkeit, das Betriebssystem zu zwingen, es zu sammeln ist das Betriebssystem zu denken, um die Anwendung zu betrügen ist beendet. Um das zu tun könnten Sie vielleicht eine Dummy-Anwendung erstellen, die Ihre xps Code behandelt und aus der Hauptanwendung genannt, oder Bewegen des xps-Code in seine eigene AppDomain in Ihrem Haupt-Code kann auch genug sein.

In UpdateLayout kann das Problem nicht lösen. Nach http://support.microsoft.com/kb/942443 „, die PresentationCore vorzuladen. dLL-Datei oder die PresentationFramework.dll Datei in der primären Anwendungsdomäne“benötigt wird.

Interessant. Das Problem ist nach wie vor in .NET Framework 4.0. Mein Code undicht war grimmig.

Die vorgeschlagene fix - wo UpdateLayout in einer Schleife unmittelbar nach der Erstellung des FixedDocumentSequence genannt wird, das Problem für mich nicht auf einem 400 Seite Testdokument beheben.

Allerdings hat die folgende Lösung, das Problem für mich zu beheben. Wie in der vorangegangenen fixen zog ich um den Anruf zu GetFixedDocumentSequence () außerhalb der für-jeden-Seite-Schleife. Die „Verwendung“ Klausel ... angemessene Warnung, die ich bin immer noch nicht sicher, ob es korrekt ist. Aber es ist nicht zu verletzen. Das Dokument wird anschließend wiederverwendet Seite Vorschauen auf dem Bildschirm zu erzeugen. So scheint es nicht weh 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 ist die Fix Linie geht in meiner GetXpsPageAsBitmap Routine (ommited für Klarheit), was zuvor geschrieben Code ziemlich identisch ist.

Danke an alle, die dazu beigetragen haben.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top