gpt4 book ai didi

c# - ElementHost + FlowDocument = GC 不工作,内存不断增加

转载 作者:太空狗 更新时间:2023-10-29 23:32:41 37 4
gpt4 key购买 nike

[更新,见底部!]

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

应用程序的作用

当我按下 button1 :

  • UserControl1其中只包含一个 FlowDocumentReader创建并设置为 ElementHostChild
  • FlowDocument从文本文件创建(它只包含一个 FlowDocument 和一个 StackPanel 和几千行 <TextBox/> )
  • FlowDocumentReaderDocument属性设置为此 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 blah

    任何见解表示赞赏。

    另一个更新 - 已解决(有点)

    我刚刚在另一个不使用 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/

    37 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com