[更新,见底部!]
我们的 WinForms 应用程序FlowDocumentReader
在ElementHost
. 我在一个简单的项目中重新创建了这个问题并添加了下面的代码。
应用程序的作用
当我按下button1
:
UserControl1
仅包含 a 的AFlowDocumentReader
被创建并设置为ElementHost
'sChild
- A
FlowDocument
是从一个文本文件创建的(它只包含一个FlowDocument
带有StackPanel
几千行的 a<TextBox/>
) - 的
FlowDocumentReader
属性Document
设置为此FlowDocument
此时,页面呈现FlowDocument
正确。正如预期的那样,使用了大量内存。
问题
如果
button1
再次单击,内存使用量会增加,并且每次重复该过程时都会不断增加!尽管正在使用大量新内存,但 GC 并未收集!没有不应该存在的引用,因为:如果我按下
button2
which setelementHost1.Child
为 null 并调用 GC(参见下面的代码),会发生另一件奇怪的事情 - 它不会清理内存,但如果我继续点击它几秒钟,它最终会释放它!
对于我们来说,所有这些内存都被使用是不可接受的。此外,ElementHost
从Controls
集合中删除Disposing
它,将引用设置为 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
每次按下按钮时重用、处理和重新创建它。虽然这确实有点帮助,但从某种意义上说,当您点击按钮 1 时,内存会上升和下降,而不是仅仅上升,它仍然不能解决问题 - 内存总体上会上升,并且没有被释放表格已关闭。所以现在我要悬赏。
由于似乎对这里的问题有些混淆,以下是重现泄漏的确切步骤:
1) 打开任务管理器
2)点击“开始”按钮打开表格
3)垃圾邮件十几或两次点击“GO”按钮并观察内存使用情况 -现在你应该注意到泄漏
4a) 关闭表格 -内存不会被释放。
或者
4b)垃圾邮件“CLEAN”按钮几次,内存将被释放,说明这不是引用泄漏,是GC/finalization问题
我需要做的是在步骤 3) 中防止泄漏,并在步骤 4a) 中释放内存。实际应用程序中没有“CLEAN”按钮,它只是显示没有隐藏的引用。
在点击“GO”按钮几次后,我使用 CLR 分析器检查内存配置文件(此时内存使用量约为 350 MB)。事实证明,有 16125 个(文档中数量的 5 倍)Controls.TextBox
和 16125 个Controls.TextBoxView
都植根于 16125 个Documents.TextEditor
对象中,这些对象植根于终结队列中 - 请参见此处:
http://i.imgur.com/m28Auix.png
任何见解表示赞赏。
另一个更新 - 已解决(有点)
我刚刚在另一个不使用 anElementHost
或 a 的纯 WPF 应用程序中再次遇到了这个问题FlowDocument
,所以回想起来,这个标题具有误导性。正如 Anton Tykhyy 所解释的,这只是 WPFTextBox
本身的一个错误,它没有正确处理其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
停留在完成队列中。