Открытие документа XPS в .Net приводит к утечке памяти

StackOverflow https://stackoverflow.com/questions/218681

  •  03-07-2019
  •  | 
  •  

Вопрос

Следующий фрагмент кода иллюстрирует утечку памяти при открытии файлов XPS.Если вы запустите его и понаблюдаете за диспетчером задач, он будет расти и не освободит память до тех пор, пока приложение не завершит работу.

'****** Запускается консольное приложение.

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

'****** Консольное приложение ЗАВЕРШАЕТСЯ.

Причина, по которой это повторяется тысячу раз, заключается в том, что мой код обрабатывает множество файлов и быстро приводит к утечке памяти, вызывая исключение OutOfMemoryException .Принудительная сборка мусора не работает (я подозреваю, что это неуправляемый фрагмент памяти во внутренних компонентах XPS).

Первоначально код был в другом потоке и классе, но был упрощен до этого.

Любая помощь очень ценится.

Райан

Это было полезно?

Решение

Что ж, я нашел это.Это ошибка в фреймворке, и чтобы обойти ее, вы добавляете вызов UpdateLayout.Оператор Using может быть изменен на следующий, чтобы обеспечить исправление;

        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

Другие советы

Столкнулся с этим сегодня.Интересно, что когда я изучил ситуацию с помощью Reflector.NET, я обнаружил, что исправление связано с вызовом UpdateLayout() в ContextLayoutManager, связанном с текущим диспетчером.(читать:нет необходимости перебирать страницы).

По сути, вызываемый код (используйте отражение здесь) является:

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

Определенно ощущается как небольшая оплошность со стороны мисс

Для ленивых или незнакомых этот код работает:

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 будет жаловаться, но, возможно, это исправлено в следующей версии фреймворка.Код, опубликованный автором, кажется "более безопасным", если вы предпочли бы не использовать отражение.

ХТХ!

Я не могу дать вам никакого авторитетного совета, но у меня действительно есть несколько мыслей:

  • Если вы хотите следить за своей памятью внутри цикла, вам также необходимо собирать память внутри цикла.В противном случае вы будете появиться для утечки памяти по замыслу, поскольку более эффективно собирать большие блоки реже (по мере необходимости), а не постоянно собирать небольшие объемы.В этом случае блок scope создает инструкцию using следует этого было бы достаточно, но вы используете GC.Сбор указывает на то, что, возможно, происходит что-то еще.
  • Даже GC.Collect - это всего лишь предложение (хорошо, очень сильный предложение, но все же предложение):это не гарантирует, что будет собрана вся оставшаяся память.
  • Если во внутреннем коде XPS действительно происходит утечка памяти, единственный способ заставить ОС собрать ее - это заставить ОС думать, что приложение завершило работу.Для этого вы, возможно, могли бы создать фиктивное приложение, которое обрабатывает ваш xps-код и вызывается из основного приложения, или, возможно, достаточно переместить xps-код в его собственный AppDomain внутри вашего основного кода.

Добавление описания обновления не может решить проблему.Согласно http://support.microsoft.com/kb/942443, "предварительно загрузите файл PresentationCore.dll или PresentationFramework.требуется файл dll в домене основного приложения".

Интересно.Проблема все еще присутствует в .net framework 4.0.Мой код сильно просачивался.

Предлагаемое исправление, при котором UpdateLayout вызывается в цикле сразу после создания последовательности FixedDocumentSequence, НЕ устранило проблему для меня в 400-страничном тестовом документе.

Однако следующее решение ДЕЙСТВИТЕЛЬНО устранило проблему для меня.Как и в предыдущих исправлениях, я переместил вызов GetFixedDocumentSequence() за пределы цикла для каждой страницы.Предложение "using"...честно предупреждаю, что я все еще не уверен, что это правильно.Но это не причиняет боли.Впоследствии документ повторно используется для создания предварительного просмотра страниц на экране.Так что, кажется, это не причиняет боли.

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

}

На самом деле строка исправления находится внутри моей процедуры GetXpsPageAsBitmap (опущена для наглядности), которая в значительной степени идентична ранее опубликованному коду.

Спасибо всем, кто внес свой вклад.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top