C# ElementHost+;FlowDocument=GC不工作,内存不断增加 [更新,见下!]

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

我们的WinForms应用程序在
ElementHost
中托管WPF
FlowDocumentReader
时内存泄漏。我在一个简单的项目中重新创建了这个问题,并添加了下面的代码

应用程序的作用 当我按下
按钮1
时:

  • 创建一个仅包含一个
    FlowDocumentReader
    UserControl1
    ,并将其设置为
    ElementHost
    的子
  • FlowDocument
    是从文本文件创建的(它只包含一个
    FlowDocument
    ,带有一个
    StackPanel
    ,有几千行
  • FlowDocumentReader
    Document
    属性设置为此
    FlowDocument
此时,页面将正确呈现
流程文档
。正如预期的那样,使用了大量内存

问题
  • 如果再次单击
    按钮1
    ,则内存使用会增加,并且每次重复该过程时都会不断增加!尽管使用了大量新内存,GC仍无法收集!没有不应该存在的引用,因为:

  • 如果我按
    按钮2
    elementHost1.Child
    设置为null并调用GC(请参见下面的代码),另一件奇怪的事情就会发生——它不会清理内存,但如果我持续单击它几秒钟,它最终会释放它

我们不能接受所有这些记忆一直被使用。另外,从
控件
集合中删除
元素主机
处理它,将引用设置为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;
        }
    }