Pergunta

O snippet de código a seguir ilustra um vazamento de memória ao abrir arquivos XPS. Se você executá -lo e assistir ao gerenciador de tarefas, ele crescerá e não liberará memória até que o aplicativo saia.

******** Começa o aplicativo de console.

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

'****** O aplicativo de console termina.

A razão pela qual ele faz milhares de vezes é porque meu código processa muitos arquivos e vazamentos de memória forçando rapidamente uma O outOfMemoryException. Forçar a coleta de lixo não funciona (suspeito que seja um pedaço de memória não gerenciado nos internos do XPS).

O código estava originalmente em outro tópico e classe, mas foi simplificado para isso.

Qualquer ajuda muito apreciada.

Ryan

Foi útil?

Solução

Bem, eu encontrei. É um bug na estrutura e, para contorná -lo, você adiciona uma chamada para o UpdateLayout. O uso da instrução pode ser alterado para o seguinte para fornecer uma correção;

        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

Outras dicas

Encontrou isso hoje. Curiosamente, quando eu olhei para as coisas usando o refletor.net, encontrei a correção envolvia a chamada updateLayout () no contextLayoutManager associado ao despachante atual. (Leia: Não há necessidade de iterar nas páginas).

Basicamente, o código a ser chamado (use reflexão aqui) é:

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

Definitivamente parece uma pequena supervisão de MS.

Para os preguiçosos ou desconhecidos, esse 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);

O FXCOP reclamará, mas talvez seja corrigido na próxima versão da estrutura. O código publicado pelo autor parece ser "mais seguro" se você preferir não usar a reflexão.

HTH!

Não posso te dar nenhum conselho de autoridade, mas tive alguns pensamentos:

  • Se você quiser assistir sua memória dentro do loop, também precisa coletar memória dentro do loop. Caso contrário, você irá aparecer vazar memória por design, pois é mais eficiente coletar blocos maiores com menos frequência (conforme necessário), em vez de estar constantemente coletando pequenas quantidades. Nesse caso, o bloco de escopo criando a instrução usando deve Seja suficiente, mas o uso do GC.Collect indica que talvez algo mais esteja acontecendo.
  • Até o gc.collect é apenas uma sugestão (ok, muito Forte sugestão, mas ainda uma sugestão): não garante que toda a memória pendente seja coletada.
  • Se o código XPS interno realmente estiver vazando memória, a única maneira de forçar o sistema operacional a cobrá -lo é enganar o sistema operacional a pensar que o aplicativo terminou. Para fazer isso, você talvez possa criar um aplicativo fictício que lida com seu código XPS e seja chamado do aplicativo principal, ou mover o código XPS para o seu próprio aplicativo dentro do seu código principal também pode ser suficiente.

Adicionar updatelayout não pode resolver o problema. De acordo com http://support.microsoft.com/kb/942443, "pré -carregue o arquivo ApresentationCore.dll ou o arquivo ApresentationFramework.dll no domínio do aplicativo primário" é necessário.

Interessante. O problema ainda está presente no .NET Framework 4.0. Meu código estava vazando ferozmente.

A correção proposta - onde o UpdateLayout é chamado em um loop imediatamente após a criação do FixedDocumentSequence não corrigiu o problema para mim em um documento de teste de 400 páginas.

No entanto, a solução a seguir resolveu o problema para mim. Como nas correções anteriores, mudei a chamada para getFixedDocumentSequence () fora do loop de cada página. A cláusula "Usando" ... Aviso justo de que ainda não tenho certeza de que está correto. Mas não está doendo. O documento é posteriormente reutilizado para gerar visualizações de página na tela. Portanto, não parece doer.

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

}

Na realidade, a linha de fixação entra na minha rotina getXPSPAGeasBitMap (Omited for Clarity), que é praticamente idêntica ao código publicado anteriormente.

Obrigado a todos que contribuíram.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top