【问题标题】:Opening XPS document in .Net causes a memory leak在 .Net 中打开 XPS 文档会导致内存泄漏
【发布时间】:2008-10-20 14:42:12
【问题描述】:

以下代码 sn-p 说明了打开 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 内部的一块非托管内存)。

代码最初在另一个线程和类中,但已被简化为这个。

非常感谢任何帮助。

瑞恩

【问题讨论】:

    标签: .net memory memory-leaks xps


    【解决方案1】:

    嗯,我找到了。这是框架中的一个错误,要解决它,您需要添加对 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
    

    【讨论】:

    • 任何证明这是一个真正的错误?是否报告了它以及重现步骤?
    • 证明在布丁中,因为他们运行它并查看(评论上面的 DirectCast,你会得到一个泄漏)。我没有通过 MS Connect 提出,而是通过 MS Social (social.msdn.microsoft.com/Forums/en-US/windowsxps/thread/…) 提出。
    • 今天遇到了同样的问题。谢谢,这解决了它!顺便说一句,您是如何找到该解决方法的?只是尝试一下?
    • 啊哈!终于有人相信我了,我没疯,你看!我现在可以离开医生吗?说真的,我不记得了。我想我只是像你说的那样摆弄了,但是我到底是怎么找到这么一个我不记得的模糊修复的。
    【解决方案2】:

    今天遇到了这个。有趣的是,当我使用 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!

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

        【解决方案4】:

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

        【讨论】:

        • 嗯,它确实解决了这个问题,虽然它不是最好的解决方案。
        【解决方案5】:

        有趣。 .net framework 4.0 中仍然存在该问题。我的代码泄漏得很厉害。

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

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

        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 例程中(为清楚起见省略了),这与之前发布的代码几乎相同。

        感谢所有做出贡献的人。

        【讨论】:

          猜你喜欢
          • 2012-02-03
          • 1970-01-01
          • 1970-01-01
          • 2023-03-14
          • 1970-01-01
          • 2011-05-18
          • 2015-09-02
          • 2022-10-17
          • 2021-03-23
          相关资源
          最近更新 更多