6

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

该代码最初位于另一个线程和类中,但已简化为此。

非常感谢任何帮助。

瑞安

4

5 回答 5

7

嗯,我找到了。这是框架中的一个错误,要解决它,您需要添加对 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
于 2008-10-20T17:03:08.053 回答
5

今天碰到这个。有趣的是,当我使用 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 会抱怨,但可能会在下一个框架版本中修复。如果您不想使用反射,作者发布的代码似乎“更安全”。

于 2010-03-09T16:23:39.477 回答
0

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

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

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

于 2010-12-08T02:59:59.520 回答
0

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

感谢所有做出贡献的人。

于 2011-08-18T05:39:30.567 回答