C# ElementHost+;FlowDocument=GC不工作,内存不断增加 [更新,见下!]
我们的WinForms应用程序在C# ElementHost+;FlowDocument=GC不工作,内存不断增加 [更新,见下!],c#,wpf,winforms,flowdocument,elementhost,C#,Wpf,Winforms,Flowdocument,Elementhost,我们的WinForms应用程序在ElementHost中托管WPFFlowDocumentReader时内存泄漏。我在一个简单的项目中重新创建了这个问题,并添加了下面的代码 应用程序的作用 当我按下按钮1时: 创建一个仅包含一个FlowDocumentReader的UserControl1,并将其设置为ElementHost的子 FlowDocument是从文本文件创建的(它只包含一个FlowDocument,带有一个StackPanel,有几千行) FlowDocumentReader的Do
ElementHost
中托管WPFFlowDocumentReader
时内存泄漏。我在一个简单的项目中重新创建了这个问题,并添加了下面的代码
应用程序的作用
当我按下按钮1
时:
- 创建一个仅包含一个
的FlowDocumentReader
,并将其设置为UserControl1
的子ElementHost
是从文本文件创建的(它只包含一个FlowDocument
,带有一个FlowDocument
,有几千行StackPanel
)
的FlowDocumentReader
属性设置为此Document
FlowDocument
流程文档
。正如预期的那样,使用了大量内存
问题
- 如果再次单击
,则内存使用会增加,并且每次重复该过程时都会不断增加!尽管使用了大量新内存,GC仍无法收集!没有不应该存在的引用,因为:按钮1
- 如果我按
将按钮2
设置为null并调用GC(请参见下面的代码),另一件奇怪的事情就会发生——它不会清理内存,但如果我持续单击它几秒钟,它最终会释放它elementHost1.Child
控件
集合中删除元素主机
,处理它,将引用设置为null,然后调用GC不会释放内存
我想要什么
- 如果多次单击
按钮1
,则内存使用量不应继续增加
- 我应该能够释放所有内存(这只是“真实”应用程序中的一个窗口,我希望在它关闭时执行此操作)
这不是内存使用不重要的事情,我可以让GC随时收集它。事实上,它明显地降低了机器的速度
代码
如果您想下载VS项目,我已将其上传到此处:
否则,以下是相关代码。只需在设计器中向表单添加2个按钮,并将它们连接到事件。表格1.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>
其中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;
}
}
}
}
公共静态IEnumerable FindVisualChildren(DependencyObject depObj),其中T:DependencyObject
{
if(depObj!=null)
{
for(int i=0;i
基本上,我做文本框
应该做的事情。最后,我还调用了GC.Collect()
(严格来说这不是必需的,但有助于更快地释放内存)。这是一个非常丑陋的解决方案,但似乎解决了问题。没有更多的textdeditors
卡在最终确定队列中。我在这里找到了这篇博文:
因此,请在按钮2单击事件中尝试以下操作:
if (elementHost1 != null)
{
elementHost1.Child = null;
elementHost1.Dispose();
elementHost1.Parent = null;
elementHost1 = null;
}
我发现在此之后调用GC.Collect()可能不会立即减少内存使用,但在某一点之后不会增加。为了更好地复制,我制作了第二个表单,它打开了您的Form1
。有了这个,我试着打开你的表单大约20次,总是点击按钮1,然后点击按钮2,然后关闭表单,内存使用保持不变
编辑:奇怪的是,再次打开表单后,内存似乎被释放了,而不是在GC.Collect()上。我忍不住发现这是ElementHost
控件的一个bug
编辑2,我的表格1
:
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(),我也不会再经历任何内存泄漏。记住,我曾多次尝试从另一个窗体打开此窗体。的确,PresentationFramework.dll!System.Windows.Documents.TextEditor
有一个终结器,因此它会卡在终结器队列上(连同挂在它上面的所有东西),除非正确处理。我在PresentationFramework.dll中搜索了一下,不幸的是,我不知道如何让TextBox
es处理它们附加的TextEditor
s。对TextBox.OnDetach
的唯一相关调用位于TextBoxBase.InitializeTextContainer()
中。在这里,您可以看到,一旦文本框
创建了文本编辑器
,它只会处理它,作为创建新编辑器的交换。textdeditor
处理自身的另外两种情况是应用程序域卸载或WPF dispatcher关闭。前者看起来更有希望,因为我发现无法重新启动关闭的WPF调度程序。WPF对象不能在应用程序域之间直接共享,因为它们不是从MarshalByRefObject
派生的,而是Windows窗体控件派生的。尝试将ElementHost
放在单独的应用程序域中,并在清除表单()时将其拆下。另一种方法是使用MAF加载项将WPF控件放入不同的应用程序域;请参阅。StringReader
和XmlTextReader
都有Close()方法,我想说调用它们不会有什么坏处,即使在using块中也是如此。除此之外,我可以重现您的问题,您是否尝试过使用类似的内存分析器?我发现这很有帮助,但是我现在没有把它安装在我开的机器上。你在哪里展示
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;
}
}