2008-10-20 87 views
5

以下代码片段说明打开XPS文件时的内存泄漏。如果您运行它并观察任务管理器,它将会增长并且不会释放内存,直到应用程序退出。在.Net中打开XPS文档导致内存泄漏

'******控制台应用程序BEGINS。

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 

'****** Console application ENDS。

循环一千次的原因是因为我的代码很快处理大量文件并快速泄漏内存,强制执行OutOfMemoryException。强制垃圾收集不起作用(我怀疑它是XPS内部的非托管块内存)。

该代码原本是在另一个线程和类,但已简化为此。

任何帮助非常感谢。

瑞安

回答

6

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

任何证明这是一个真正的错误?它是否被报告,以及repro步骤? – Will 2009-03-30 21:33:30

+0

证明是在布丁中,运行它并查看(评论上面的DirectCast,你会得到一个泄漏)。我没有提出MS Connect,但与MS社交(http://social.msdn.microsoft.com/Forums/en-US/windowsxps/thread/e31edefa-b07e-450d-8ab8-5a171ee8d4c1/?ppud= 4)。 – 2009-03-31 13:47:36

0

我不能给你任何权威意见,但我确实有一些想法:

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

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

0

有趣。该问题仍然存在于.net framework 4.0中。我的代码凶狠地泄漏。

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

但是,下面的解决方案DID为我解决了这个问题。和以前的修复一样,我将调用移动到for-each-page循环之外的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程序(中省略为清楚起见),这是相当多的以前发布的代码相同的内部。

感谢所有人的贡献。