c# – ElementHost FlowDocument = GC不工作,内存不断增加
[更新,见底!]
我们的WinForms应用程序中存在内存泄漏,在ElementHost中托管WPF FlowDocumentReader.我在一个简单的项目中重新创建了这个问题,并添加了下面的代码. 该应用程序的功能 当我按下按钮1时: >创建一个仅包含FlowDocumentReader的UserControl1并将其设置为ElementHost的Child 此时,页面正确呈现FlowDocument.正如预期的那样,使用了大量内存. 问题 >如果再次单击button1,则内存使用量会增加,并且每次重复该过程时都会继续增加!尽管使用了大量新内存,GC仍未收集!没有参考不应该存在,因为: 我们所有这些记忆都被使用是不可接受的.此外,从Controls集合中删除ElementHost,Disposing it,将引用设置为null,然后调用GC不会释放内存. 我想要的是 >如果多次点击button1,内存使用量不应该继续增加 这不是内存使用无关紧要的事情,我可以随时让GC收集它.它实际上最终明显减慢了机器的速度. 代码 如果您只想下载VS项目,我已在此处上传: 否则,这是相关代码.只需在设计器中为表单添加2个按钮,然后将它们连接到事件即可. Form1.cs中: using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Windows.Documents; using System.IO; using System.Xml; using System.Windows.Markup; using System.Windows.Forms.Integration; namespace WindowsFormsApplication7 { public partial class Form1 : Form { private ElementHost elementHost; public Form1() { InitializeComponent(); } private void button1_Click(object sender,EventArgs e) { string rawXamlText = File.ReadAllText("in.txt"); using (var flowDocumentStringReader = new StringReader(rawXamlText)) using (var flowDocumentTextReader = new XmlTextReader(flowDocumentStringReader)) { if (elementHost != null) { Controls.Remove(elementHost); elementHost.Child = null; elementHost.Dispose(); } var uc1 = new UserControl1(); object document = XamlReader.Load(flowDocumentTextReader); var fd = document as FlowDocument; uc1.docReader.Document = fd; elementHost = new ElementHost(); elementHost.Dock = DockStyle.Fill; elementHost.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; Controls.Add(elementHost); elementHost.Child = uc1; } } private void button2_Click(object sender,EventArgs e) { if (elementHost != null) elementHost.Child = null; GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); } } } UserControl1.xaml <UserControl x:Class="WindowsFormsApplication7.UserControl1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> <FlowDocumentReader x:Name="docReader"></FlowDocumentReader> </UserControl> 编辑: 我终于有时间再次处理这件事了.我尝试的不是重复使用ElementHost,而是在每次按下按钮时处理并重新创建它.虽然这确实有点帮助,但是当你垃圾邮件点击button1而不是仅仅上升时内存正在上下移动时,它仍然无法解决问题 – 内存整体上升并且当它没有被释放时表格已关闭.所以现在我正在给予赏金. 由于这里似乎有一些混淆,这里有重复泄漏的确切步骤: 1)打开任务管理器 2)单击“开始”按钮打开表单 3)垃圾邮件在“GO”按钮上点击十二或两次并观察内存使用情况 – 现在您应该注意到泄漏 4a)关闭表单 – 内存不会被释放. 要么 4b)垃圾邮件“CLEAN”按钮几次,内存将被释放,表明这不是引用泄漏,这是GC /敲定问题 我需要做的是在步骤3)防止泄漏并在步骤4a)释放存储器.实际应用程序中没有“CLEAN”按钮,只是在这里显示没有隐藏的引用. 我使用CLR分析器在点击“GO”按钮几次后检查内存配置文件(此时内存使用量约为350 MB).事实证明,有16125(文档中的数量的5倍)Controls.TextBox和16125 Controls.TextBoxView都植根于16125个Documents.TextEditor对象,这些对象植根于终结队列 – 请参见此处: http://i.imgur.com/m28Aiux.png 有任何见解赞赏. 另一个更新 – 解决(种类) 我刚刚在另一个不使用ElementHost或FlowDocument的纯WPF应用程序中遇到了这个问题,所以回想起来,标题是误导性的.正如Anton Tykhyy所解释的,这只是WPF TextBox本身的一个错误,它没有正确处理它的TextEditor. 我不喜欢安东建议的解决方法,但他对这个错误的解释对我相当丑陋但很简短的解决方案很有用. 当我要销毁包含TextBoxes的控件的实例时,我这样做(在控件的代码隐藏中): var textBoxes = FindVisualChildren<TextBox>(this).ToList(); foreach (var textBox in textBoxes) { var type = textBox.GetType(); object textEditor = textBox.GetType().GetProperty("TextEditor",BindingFlags.NonPublic | BindingFlags.Instance).GetValue(textBox,null); var onDetach = textEditor.GetType().GetMethod("OnDetach",BindingFlags.NonPublic | BindingFlags.Instance); onDetach.Invoke(textEditor,null); } FindVisualChildren的位置是: public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject { if (depObj != null) { for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++) { DependencyObject child = VisualTreeHelper.GetChild(depObj,i); if (child != null && child is T) { yield return (T)child; } foreach (T childOfChild in FindVisualChildren<T>(child)) { yield return childOfChild; } } } } 基本上,我做的是TextBox应该做的事情.最后我还调用GC.Collect()(不是绝对必要但有助于更快地释放内存).这是一个非常难看的解决方案,但它似乎解决了这个问题.没有更多TextEditors卡在终结队列中. 解决方法
实际上,PresentationFramework.dll!System.Windows.Documents.TextEditor有一个终结器,所以它会被卡在终结器队列上(连同挂掉它的所有东西),除非正确处理掉.我在PresentationFramework.dll中搜索了一下,不幸的是我没有看到如何让TextBoxes处理他们附加的TextEditors.对TextBox.OnDetach的唯一相关调用是在TextBoxBase.InitializeTextContainer()中.在那里你可以看到,一旦TextBox创建了一个TextEditor,它只会处理它以换取创建一个新的. TextEditor自行处理的另外两个条件是应用程序域卸载或WPF调度程序关闭时.前者看起来更有希望,因为我发现没有办法重新启动关闭的WPF调度程序. WPF对象不能跨应用程序域直接共享,因为它们不是从MarshalByRefObject派生的,而是从Windows Forms控件派生的.尝试将您的ElementHost放在一个单独的应用程序域中,并在清除表单时将其拆除(
you may need to shut down the dispatcher first).另一种方法是使用MAF加载项将WPF控件放入不同的应用程序域;见
this SO question.
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |