[更新,见底部!]
我们托管 WPF 的 WinForms 应用程序中存在内存泄漏 FlowDocumentReader
在 ElementHost
.我在一个简单的项目中重新创建了这个问题并添加了下面的代码。
应用程序的作用
当我按下 button1
:
UserControl1
其中只包含一个 FlowDocumentReader
创建并设置为 ElementHost
的 Child
FlowDocument
从文本文件创建(它只包含一个 FlowDocument
和一个 StackPanel
和几千行 <TextBox/>
)FlowDocumentReader
的 Document
属性设置为此 FlowDocument
此时页面呈现
FlowDocument
正确。正如预期的那样,使用了大量内存。问题
button1
再次单击,内存使用量增加,并且每次重复该过程时都会增加!尽管使用了大量新内存,但 GC 没有收集!没有不应该存在的引用,因为:button2
其中集 elementHost1.Child
为 null 并调用 GC(参见下面的代码),另一个奇怪的事情发生了 - 它不会清理内存,但如果我继续点击它几秒钟,它最终会释放它! 对我们来说,所有这些内存都被使用是 Not Acceptable 。此外,删除
ElementHost
来自 Controls
收藏, Disposing
它,将引用设置为空,然后调用 GC 不会释放内存。我想要的是
button1
被多次点击,内存使用量不应继续上升 这不是内存使用无关紧要的事情,我可以让 GC 随时收集它。它实际上最终会显着降低机器的速度。
编码
如果您只想下载 VS 项目,我已将其上传到此处:
http://speedy.sh/8T5P2/WindowsFormsApplication7.zip
否则,这是相关的代码。只需在设计器中的表单中添加 2 个按钮并将它们与事件 Hook 。 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();
}
}
}
用户控件1.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/finalization问题
我需要做的是在步骤 3) 中防止泄漏并在步骤 4a) 中释放内存。 “CLEAN”按钮在实际应用中并不存在,它只是为了表明没有隐藏的引用。
在点击“GO”按钮几次后,我使用 CLR 分析器检查内存配置文件(此时内存使用量约为 350 MB)。事实证明,有 16125 个(文档中数量的 5 倍)
Controls.TextBox
和 16125 Controls.TextBoxView
两者都 Root 于 16125 Documents.TextEditor
根植于终结队列的对象 - 请参阅此处:http://i.imgur.com/m28Aiux.png
任何见解表示赞赏。
另一个更新 - 已解决(有点)
我刚刚在另一个不使用
ElementHost
的纯 WPF 应用程序中再次遇到了这个问题。或 FlowDocument
,所以回想起来,标题是误导性的。正如 Anton Tykhyy 所解释的,这只是 WPF TextBox
的一个错误。本身,它没有正确处理它的 TextEditor
.我不喜欢 Anton 建议的解决方法,但他对错误的解释对我相当丑陋但简短的解决方案很有用。
当我将要销毁包含
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
卡在完成队列中。
最佳答案
我在这里找到了这篇博文:Memory Leak while using ElementHost when using a WPF User control inside a Windows Forms project
所以,在你的 Button2 点击事件中试试这个:
if (elementHost1 != null)
{
elementHost1.Child = null;
elementHost1.Dispose();
elementHost1.Parent = null;
elementHost1 = null;
}
我发现在此之后调用 GC.Collect() 可能不会立即减少内存使用量,但它不会在某个点之后增加。为了更好地再现,我制作了第二个表格,它打开您的
Form1
.有了这个,我尝试打开你的表单大约 20 次,总是点击 Button1 然后 Button2 然后关闭表单,内存使用量保持不变。编辑:奇怪的是,内存似乎在再次打开表单后被释放,而不是在 GC.Collect() 上。我不禁发现这是
ElementHost
的一个错误控制。Edit2,我的
Form1
:public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
m_uc1 = new UserControl1();
elementHost1.Child = m_uc1;
}
private UserControl1 m_uc1;
private void button1_Click(object sender, EventArgs e)
{
string rawXamlText = File.ReadAllText(@"in.txt");
var flowDocumentStringReader = new StringReader(rawXamlText);
var flowDocumentTextReader = new XmlTextReader(flowDocumentStringReader);
object document = XamlReader.Load(flowDocumentTextReader);
var fd = document as FlowDocument;
m_uc1.docReader.Document = fd;
flowDocumentTextReader.Close();
flowDocumentStringReader.Close();
flowDocumentStringReader.Dispose();
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (elementHost1 != null)
{
elementHost1.Child = null;
elementHost1.Dispose();
elementHost1.Parent = null;
elementHost1 = null;
}
}
即使没有明确的 GC.Collect() 我也不会再遇到任何内存泄漏。请记住,我尝试从另一个表单多次打开此表单。
关于c# - ElementHost + FlowDocument = GC 不工作,内存不断增加,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14757527/