以下代码片段说明了打开 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 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注意事物时,我发现修复涉及在与当前Dispatcher关联的ContextLayoutManager上调用UpdateLayout()。 (阅读:无需迭代页面)。

基本上,要调用的代码(在此使用反射)是:

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

绝对感觉像是MS的一个小疏忽。

对于懒惰或不熟悉,此代码有效:

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会抱怨,但也许它已在下一个框架版本中得到修复。作者发布的代码似乎“更安全”。如果你不想使用反射。

HTH!

我无法给你任何权威的建议,但我确实有一些想法:

  • 如果您想查看循环内的内存,则还需要收集循环内的内存。否则你会 出现 通过设计泄漏内存,因为较少频率地(根据需要)收集较大的块比不断收集少量的块更有效。在本例中,作用域块创建了 using 语句 应该 已经足够了,但是您对 GC.Collect 的使用表明可能还发生了其他事情。
  • 甚至 GC.Collect 只是一个建议(好吧,非常 强的 建议,但仍然是建议):它不保证收集所有未完成的内存。
  • 如果内部 XPS 代码确实泄漏内存,则强制操作系统收集内存的唯一方法是欺骗操作系统,使其认为应用程序已结束。为此,您也许可以创建一个虚拟应用程序来处理您的 xps 代码并从主应用程序调用,或者将 xps 代码移动到主代码内它自己的 AppDomain 中也可能就足够了。

添加UpdateLayout无法解决问题。 根据 http://support.microsoft.com/kb/942443 ,“预加载PresentationCore .dll文件或主应用程序域中的PresentationFramework.dll文件“需要。

有趣。问题仍然存在于.net框架4.0中。我的代码凶猛地泄漏了。

建议的修复 - 在创建FixedDocumentSequence后立即在循环中调用UpdateLayout并没有在400页测试文档中解决我的问题。

但是,以下解决方案DID为我解决了这个问题。与之前的修复一样,我将调用移到了for-each-pages循环之外的GetFixedDocumentSequence()。 “使用”条款......公平警告,我仍然不确定它是否正确。但它并没有伤害。随后,该文档将重新用于在屏幕上生成页面预览。所以它似乎没有受伤。

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