2008-10-20 58 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程序(中省略爲清楚起見),這是相當多的以前發佈的代碼相同的內部。

感謝所有人的貢獻。