qué - La apertura de un documento XPS en.Net provoca una pérdida de memoria
qué es un documento en xps (5)
Bueno, lo encontré. ES un error en el marco y para solucionarlo, agrega 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
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 finalice.
''****** La aplicación de consola COMIENZA.
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
''****** Concluye la aplicación de la consola.
La razón por la que se repite mil veces es porque mi código procesa muchos archivos y pierde memoria forzando rápidamente una OutOfMemoryException. Forzar la recolección de basura no funciona (sospecho que es un trozo de memoria no administrado en las partes internas de XPS).
El código originalmente estaba en otro hilo y clase pero se ha simplificado a esto.
Cualquier ayuda muy apreciada.
Ryan
No puedo darte ningún consejo autorizado, pero tuve algunas ideas:
- Si desea ver su memoria dentro del bucle, también debe recopilar memoria dentro del bucle. De lo contrario, parecerá que se pierde 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 instrucción de uso debería ser suficiente, pero el uso de GC.Collect indica que tal vez está sucediendo algo más.
- Incluso GC.Collect es solo una sugerencia (bien, sugerencia muy fuerte , 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ñar al sistema operativo para que crea que la aplicación ha finalizado. Para hacer eso, quizás puedas crear una aplicación ficticia que maneje tu código xps y se llame desde la aplicación principal, o mover el código xps a su propio Dominio de Aplicación dentro de tu código principal también sea suficiente.
Me encontré con esto hoy. Curiosamente, cuando analicé cosas usando Reflector.NET, encontré la solución relacionada llamando a UpdateLayout () en el ContextLayoutManager asociado con el Dispatcher actual. (léase: 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 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 se solucione en la próxima versión del framework. El código publicado por el autor parece ser "más seguro" si prefiere no usar el reflejo.
HTH!
Agregar UpdateLayout no puede resolver el problema. De acuerdo con http://support.microsoft.com/kb/942443 , "precargar el archivo PresentationCore.dll o el archivo PresentationFramework.dll en el dominio de 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 solucionó el problema en un documento de prueba de 400 páginas.
Sin embargo, la siguiente solución me solucionó el problema. Al igual que en las correcciones anteriores, moví la llamada a GetFixedDocumentSequence () fuera del ciclo for-each-page. La cláusula "usar" ... advertencia justa de que aún no estoy seguro de que sea correcta. Pero no está lastimando. El documento se vuelve a utilizar posteriormente para generar vistas previas de página en pantalla. Entonces 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 reparación va dentro de mi rutina GetXpsPageAsBitmap (omitida para mayor claridad), que es prácticamente idéntica al código publicado anteriormente.
Gracias a todos los que contribuyeron.