我们的WinForms应用程序中存在内存泄漏,在ElementHost中托管WPF flowdocumentreader.我在一个简单的项目中重新创建了这个问题,并添加了下面的代码.
该应用程序的功能
当我按下按钮1时:
>创建一个仅包含flowdocumentreader的UserControl1并将其设置为ElementHost的Child
>从文本文件创建FlowDocument(它只包含一个带有StackPanel的FlowDocument,其中包含几千行< TextBox />)
> flowdocumentreader的Document属性设置为此FlowDocument
此时,页面正确呈现FlowDocument.正如预期的那样,使用了大量内存.
问题
>如果再次单击button1,则内存使用量会增加,并且每次重复该过程时都会继续增加!尽管使用了大量新内存,GC仍未收集!没有参考不应该存在,因为:
>如果我按下button2将elementHost1.Child设置为null并调用GC(参见下面的代码),会发生另一个奇怪的事情 – 它不会清理内存,但如果我一直点击它几秒钟,它最终会免费吧!
我们所有这些记忆都被使用是不可接受的.此外,从Controls集合中删除ElementHost,disposing it,将引用设置为null,然后调用GC不会释放内存.
我想要的是
>如果多次点击button1,内存使用量不应该继续增加
>我应该能够释放所有内存(这只是“真实”应用程序中的一个窗口,我希望在关闭时执行此操作)
这不是内存使用无关紧要的事情,我可以随时让GC收集它.它实际上最终明显减慢了机器的速度.
如果您只想下载VS项目,我已在此处上传:
http://speedy.sh/8T5P2/WindowsFormsApplication7.zip
否则,这是相关代码.只需在设计器中为表单添加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卡在终结队列中.
解决方法
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。