Pregunta

El siguiente fragmento de código ilustra una pérdida de memoria al abrir archivos XPS. Si lo ejecuta y mira el administrador de tareas, crecerá y no liberará memoria hasta que la aplicación se cierre.

'****** COMIENZA la aplicación de consola.

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

'****** La aplicación de consola termina.

La razón por la que se repite mil veces es porque mi código procesa muchos archivos y pierde memoria rápidamente forzando una excepción OutOfMemoryException. Forzar la recolección de basura no funciona (sospecho que es una porción de memoria no administrada en las partes internas de XPS).

El código estaba originalmente en otro hilo y clase pero se ha simplificado a esto.

Cualquier ayuda muy apreciada.

Ryan

¿Fue útil?

Solución

Bueno, lo encontré. ES un error en el marco y para solucionarlo, agregue una llamada a UpdateLayout. El uso de la declaración se puede cambiar a lo siguiente para proporcionar una solución;

        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

Otros consejos

Me encontré con esto hoy. Curiosamente, cuando miré las cosas usando Reflector.NET, encontré que la solución implicaba llamar a UpdateLayout () en el ContextLayoutManager asociado con el Dispatcher actual. (lea: no es necesario iterar sobre las páginas).

Básicamente, el código que se llamará (use la reflexión aquí) es:

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

Definitivamente se siente como un pequeño descuido por parte de MS.

Para los perezosos o desconocidos, este código funciona:

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 se quejará, pero tal vez esté solucionado en la próxima versión del framework. El código publicado por el autor parece ser "más seguro" si prefiere no usar la reflexión.

HTH!

No puedo darle ningún consejo autorizado, pero tuve algunas ideas:

  • Si desea ver su memoria dentro del ciclo, también debe estar recolectando memoria dentro del ciclo. De lo contrario, parecerá perder memoria por diseño, ya que es más eficiente recolectar bloques más grandes con menos frecuencia (según sea necesario) en lugar de recolectar constantemente pequeñas cantidades. En este caso, el bloque de alcance que crea la declaración de uso debería ser suficiente, pero su uso de GC.Collect indica que tal vez algo más está sucediendo.
  • Incluso GC.Collect es solo una sugerencia (bueno, muy fuerte sugerencia, pero sigue siendo una sugerencia): no garantiza que se recopile toda la memoria pendiente.
  • Si el código XPS interno realmente está perdiendo memoria, la única forma de obligar al sistema operativo a recopilarlo es engañando al sistema operativo para que piense que la aplicación ha finalizado. Para hacer eso, tal vez podría crear una aplicación ficticia que maneje su código xps y se llame desde la aplicación principal, o mover el código xps a su propio AppDomain dentro de su código principal también puede ser suficiente.

Agregar UpdateLayout no puede resolver el problema. De acuerdo con http://support.microsoft.com/kb/942443 , " precargar el PresentationCore .dll o el archivo PresentationFramework.dll en el dominio de la aplicación principal " es necesario.

Interesante. El problema todavía está presente en .net framework 4.0. Mi código estaba goteando ferozmente.

La solución propuesta: donde UpdateLayout se llama en un bucle inmediatamente después de la creación de FixedDocumentSequence NO me solucionó el problema en un documento de prueba de 400 páginas.

Sin embargo, la siguiente solución me solucionó el problema. Como en arreglos anteriores, moví la llamada a GetFixedDocumentSequence () fuera del ciclo de cada página. El " utilizando " cláusula ... advertencia justa de que todavía no estoy seguro de que sea correcto. Pero no duele. El documento se reutiliza posteriormente para generar vistas previas de página en pantalla. Por lo tanto, no parece doler.

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 realidad, la línea de arreglo va dentro de mi rutina GetXpsPageAsBitmap (omitida por claridad), que es prácticamente idéntica al código publicado anteriormente.

Gracias a todos los que contribuyeron.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top