C#中的FlowDocument内存问题

我目前正在尝试处理释放FlowDocument资源的问题。 我正在加载一个rtf文件并将其放入带有TextRange.Load的FlowDocument中。 我注意到,在它执行此操作后,它会保留这些资源,GC不会收集它。 我已经运行了一个内存分析器,并且已经看到这是真的。 我还把它缩小到我加载实际将rtf放入FlowDocument。 如果我不这样做,那么一切都很好。 所以我知道这是问题所在。

我希望对如何解决这个问题提供一些指导。 这是加载rtf和所有内容的代码。 我已经评论了所有其他代码,甚至把它放在自己的范围内,并尝试了GC.Collect()。 任何帮助是极大的赞赏。

编辑:这是我目前的完整代码。 除了最基本的要素之外,我已经取出了其他所有东西来让它运行起来。 问题仍然存在。 如您所见,FlowDocument和TextRange未在其他任何地方引用。

public LoadRTFWindow(string file) { InitializeComponent(); using (FileStream reader = new FileStream(file, FileMode.Open)) { FlowDocument doc = new FlowDocument(); TextRange range = new TextRange(doc.ContentStart, doc.ContentEnd); range.Load(reader, System.Windows.DataFormats.Rtf); } GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); } 

我发现这篇文章 ,我希望能帮助我解决我的问题,但我没有运气。 非常感谢任何类型的帮助。 谢谢。

编辑:我想我应该提到我检查这个的主要方式。 我打开了Windows任务管理器,正在观察应用程序进程正在使用的内存使用情况。 当我运行上面的代码时,应用程序在执行TextRange.Load()时会从40,000K变为70,000K(这是一个400页的大型RTF),一旦完成,它就会下降到61,000K并保持在那里。 我的期望是它会回落到40,000K或者至少非常接近它。

正如我之前提到的,我使用了一个内存分析器,看到有很多段落,Run..ect。 事后仍然活着。

如果我确认存在内存泄漏,我将采取以下措施来调试问题。

  1. 从http://www.microsoft.com/whdc/devtools/debugging/installx86.mspx#a安装Windows调试工具
  2. 从安装目录中启动Windbg。
  3. 启动应用程序并执行泄漏内存的操作。
  4. 将Windbg附加到您的应用程序(F6)。
  5. .loadby sos mscorwks
  6. 输入!dumpheap -type FlowDocument
  7. 检查以上命令的结果。 如果您看到多个FlowDocuments,对于第一列的每个值(包含地址),请执行

输入!gcroot

这应该会告诉你谁在坚持参考。

我们有一个类似的问题,我们在不同的线程中创建流文档,我在内存分析器中注意到对象仍然存在。

据我所知,正如此链接所述

“当创建FlowDocument时,还会在其StructuralCache中为它创建相对昂贵的格式化上下文对象。当您在紧密循环中创建多个FlowDoc时,会为每个FlowDoc创建一个StructuralCache。让我们在结束时调用Gc.Collect。循环,希望恢复一些内存.BrondeCache有一个终结器释放这个格式化上下文,但不是立即。终结器有效地安排一个操作来释放DispatcherPriority.Background的上下文。“

因此,在调度程序操作完成之前,Flow文档将在内存中。 所以想法是完成调度员操作。

如果您在Dispatcher当前正在运行的线程中,请尝试下面的代码,它将强制完成队列中的所有操作,因为SystemIdle是最低优先级:

 Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.SystemIdle, new DispatcherOperationCallback(delegate { return null; }), null); 

如果你在Dispatcher没有运行的线程中,就像在我的情况下只在线程中创建单个流文档,所以我尝试了类似的东西:

 var dispatcher = Dispatcher.CurrentDispatcher; dispatcher.BeginInvokeShutdown(DispatcherPriority.SystemIdle); Dispatcher.Run(); 

这将在最后排队关闭,然后运行调度程序清理FlowDocument,最后它将关闭调度程序。

FlowDocument使用System.Windows.Threading.Dispatcher来释放所有资源。 它不使用终结器,因为终结器将阻塞当前线程,直到所有资源都是空闲的。 因此用户可能会看到一些UI冻结等等。 调度程序在后台线程中运行,对UI的影响较小。
所以调用GC.WaitForPendingFinalizers(); 对此没有影响。 您只需添加一些代码来等待并允许Dispatchers完成其工作。 只是尝试添加像Thread.CurrentThread.Sleep(2000 / * 2秒* /);

编辑:在我看来,你在一些应用程序的调试过程中发现了这个问题。 我编写了以下非常简单的测试用例(控制台程序):

  static void Main(string[] args) { Console.WriteLine("press enter to start"); Console.ReadLine(); var path = "c:\\1.rtf"; for (var i = 0; i < 20; i++) { using (var stream = new FileStream(path, FileMode.Open)) { var document = new FlowDocument(); var range = new TextRange(document.ContentStart, document.ContentEnd); range.Load(stream, DataFormats.Rtf); } } Console.WriteLine("press enter to run GC."); Console.ReadLine(); GC.Collect(); GC.WaitForPendingFinalizers(); Console.WriteLine("GC has finished ."); Console.ReadLine(); } 

我试图重现这个问题。 我运行了好几次,它运行得很好 - 没有泄漏(总共大约3,2Kb和36个手柄)。 直到我在VS中以调试模式运行该程序时才能重现它(只需f5而不是ctrl + f5)。 我在开始时收到20,5Kb,在加载之后和GC之前收到31,7Kb,在GC之后收到31Kb。 它看起来与您的结果类似。
那么,你可以尝试重现在VS下不在发布模式下运行的这个问题吗?

我以前做过#7。 这是我第一次使用Windbg,因此我不知道如何处理地址以查找引用。 这是我得到的。

  Address MT Size 0131c9c0 55cd21d8 84 013479e0 55cd21d8 84 044dabe0 55cd21d8 84 total 3 objects Statistics: MT Count TotalSize Class Name 55cd21d8 3 252 System.Windows.Documents.FlowDocument Total 3 objects 0:011> !gcroot 0131c9c0 Note: Roots found on stacks may be false positives. Run "!help gcroot" for more info. Scan Thread 0 OSTHread 47c Scan Thread 2 OSTHread be8 Scan Thread 4 OSTHread 498 DOMAIN(001657B0):HANDLE(WeakSh):911788:Root:0131ff98(System.EventHandler)-> 0131fcd4(System.Windows.Documents.AdornerLayer)-> 012fad68(MemoryTesting.Window2)-> 0131c9c0(System.Windows.Documents.FlowDocument) DOMAIN(001657B0):HANDLE(WeakSh):911cb0:Root:0131ca90(MS.Internal.PtsHost.PtsContext)-> 0131cb14(MS.Internal.PtsHost.PtsContext+HandleIndex[])-> 0133d668(MS.Internal.PtsHost.TextParagraph)-> 0131c9c0(System.Windows.Documents.FlowDocument) DOMAIN(001657B0):HANDLE(WeakSh):9124a8:Root:01320a2c(MS.Internal.PtsHost.FlowDocumentPage)-> 0133d5d0(System.Windows.Documents.TextPointer)-> 0131ca14(System.Windows.Documents.TextContainer)-> 0131c9c0(System.Windows.Documents.FlowDocument) 

(我把它放在代码块中以便于阅读)。 这是在我关闭窗口之后。 所以它似乎被一些东西引用。 现在,我知道如何释放这些引用,以便他们可以释放FlowDocument。

谢谢你的帮助。 我觉得我终于获得了一些支持。

确保FlowDocument的Parent不会闲置,请参阅此处 。 “实例化FlowDocument会自动生成托管内容的父FlowDocumentPageViewer。” 如果该控件闲置,它可能是您的问题的根源。

GC.Collect()本身不会收集所有内容,您需要运行:

 GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); 

此外,我发现运行时并不总是立即释放收集的内存,您应该检查实际的堆大小而不是依赖于任务管理器。

考虑释放该文件句柄。 另外考虑使用“using”语句而不是调用IDisposable.Dispose(没有双关语)。

我试图重现你的问题,但它不会发生在我的机器上。

任务管理器显示工作集大小,这不是程序内存使用情况的准确表示。 尝试使用perfmon代替。

  1. 开始 – >运行 – > perfmon.msc
  2. 为您的应用程序添加所有堆中的.NET CLR内存/#字节

现在重复实验并检查该计数器是否继续上升。 如果没有,则表示您没有泄漏任何托管内存。