Question

L'extrait de code suivant illustre une fuite de mémoire lors de l'ouverture de fichiers XPS. Si vous l'exécutez et surveillez le gestionnaire de tâches, il s'agrandira et ne libérera pas de mémoire jusqu'à la fermeture de l'application.

'****** L'application de la console COMMENCE.

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

'****** FIN de l'application console.

La raison pour laquelle elle effectue des boucles mille fois est que mon code traite beaucoup de fichiers et perd de la mémoire rapidement, forçant une exception OutOfMemoryException. Forcer le ramassage des ordures ne fonctionne pas (je suppose que c'est un bloc de mémoire non géré dans les composants internes XPS).

Le code était à l'origine dans un autre thread et une autre classe, mais a été simplifié.

Toute aide grandement appréciée.

Ryan

Était-ce utile?

La solution

Eh bien, je l'ai trouvé. C’est un bogue dans le framework et pour le contourner, vous ajoutez un appel à UpdateLayout. L’utilisation de statement peut être modifiée comme suit pour fournir un correctif;

        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

Autres conseils

Ran dans cette aujourd'hui. Il est intéressant de noter que lorsque je me suis intéressé à Reflector.NET, j’ai trouvé que le correctif impliquait d’appeler UpdateLayout () sur le ContextLayoutManager associé au Dispatcher actuel. (lire: inutile de parcourir les pages).

En gros, le code à appeler (utilisez la réflexion ici) est:

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

Cela ressemble vraiment à un petit oubli de la part de MS.

Pour les paresseux ou les inconnus, ce code fonctionne:

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 va se plaindre, mais c'est peut-être corrigé dans la prochaine version du framework. Le code publié par l'auteur semble être "plus sûr". si vous préférez ne pas utiliser la réflexion.

HTH!

Je ne peux vous donner aucun conseil faisant autorité, mais j’ai eu quelques réflexions:

  • Si vous souhaitez surveiller votre mémoire dans la boucle, vous devez également la collecter. Sinon, vous aurez l'impression de perdre de la mémoire, car il est plus efficace de collecter des blocs plus volumineux moins souvent (au besoin) plutôt que de collecter constamment de petites quantités. Dans ce cas, le bloc de portée créant l’instruction using devrait suffire, mais votre utilisation de GC.Collect indique qu’il se peut que quelque chose d’autre se passe.
  • Même GC.Collect n'est qu'une suggestion (d'accord, suggestion très forte , mais toujours une suggestion): cela ne garantit pas que toute la mémoire en suspens est collectée.
  • Si le code XPS interne contient réellement une fuite de mémoire, le seul moyen de forcer le système d'exploitation à le collecter est de le faire croire à la fin de l'application. Pour ce faire, vous pourriez peut-être créer une application factice qui gère votre code xps et qui est appelée à partir de l'application principale, ou déplacer le code xps dans son propre AppDomain dans votre code principal peut également suffire.

Ajouter UpdateLayout ne peut pas résoudre le problème. Selon http://support.microsoft.com/kb/942443 , préchargez le fichier PresentationCore. Fichier .dll ou PresentationFramework.dll du domaine d’application principal " est nécessaire.

Intéressant. Le problème est toujours présent dans .net Framework 4.0. Mon code fuyait férocement.

Le correctif proposé - où UpdateLayout est appelé dans une boucle immédiatement après la création de FixedDocumentSequence NE résout PAS le problème pour moi sur un document de test de 400 pages.

Cependant, la solution suivante a résolu le problème pour moi. Comme dans les correctifs précédents, j'ai déplacé l'appel à GetFixedDocumentSequence () en dehors de la boucle for-each-page. Le " using " clause ... avertissement juste que je ne suis toujours pas sûr que ce soit correct. Mais ça ne fait pas mal. Le document est ensuite réutilisé pour générer des aperçus de page à l'écran. Donc, cela ne semble pas faire mal.

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...
    }

}

En réalité, la ligne de correction va dans ma routine GetXpsPageAsBitmap (ommited pour plus de clarté), ce qui est pratiquement identique au code précédemment posté.

Merci à tous ceux qui ont contribué.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top