svi*_*nja 5 c# wpf elementhost flowdocument winforms
有一个在我们的WinForms托管WPF应用程序内存泄漏FlowDocumentReader
的一个ElementHost
.我在一个简单的项目中重新创建了这个问题,并添加了下面的代码.
当我按下button1
:
UserControl1
只包含a的A FlowDocumentReader
被创建并设置为ElementHost
'sChild
FlowDocument
是从文本文件创建的(它只包含一个FlowDocument
带有StackPanel
几千行的文件<TextBox/>
)FlowDocumentReader
的Document
属性设置为这FlowDocument
此时,页面呈现FlowDocument
正确.正如预期的那样,使用了大量内存.
如果button1
再次单击,则内存使用量会增加,并且每次重复该过程时都会不断增加!尽管使用了大量新内存,GC仍未收集!没有参考不应该存在,因为:
如果我按下button2
哪个设置elementHost1.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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
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>
Run Code Online (Sandbox Code Playgroud)
我终于有时间再次处理这件事了.我尝试的是ElementHost
每次按下按钮时重复使用,处理和重新创建它.虽然这确实有点帮助,但是当你垃圾邮件点击button1而不是仅仅上升时内存正在上下移动时,它仍然无法解决问题 - 内存整体上升并且当它没有被释放时表格已关闭.所以现在我正在给予赏金.
由于这里似乎有一些混淆,这里有重复泄漏的确切步骤:
1)打开任务管理器
2)单击"开始"按钮打开表单
3)垃圾邮件在"GO"按钮上点击十二或两次并观察内存使用情况 - 现在您应该注意到泄漏
4a)关闭表单 - 内存不会被释放.
要么
4b)垃圾邮件"CLEAN"按钮几次,内存将被释放,表明这不是引用泄漏,这是GC /敲定问题
我需要做的是在步骤3)防止泄漏并在步骤4a)释放存储器.实际应用程序中没有"CLEAN"按钮,只是在这里显示没有隐藏的引用.
我使用CLR分析器在点击"GO"按钮几次后检查内存配置文件(此时内存使用量约为350 MB).事实证明,有16125(文档中的数量的5倍)Controls.TextBox
和16125 Controls.TextBoxView
都植根于16125个Documents.TextEditor
根目录在终结队列中的对象 - 请参阅此处:
http://i.imgur.com/m28Aiux.png
有任何见解赞赏.
我刚刚在另一个不使用a ElementHost
或a的纯WPF应用程序中遇到了这个FlowDocument
,所以回想起来,标题是误导性的.正如Anton Tykhyy所解释的,这只是WPF TextBox
本身的一个错误,它没有正确处理它TextEditor
.
我不喜欢安东建议的解决方法,但他对这个错误的解释对我相当丑陋但很简短的解决方案很有用.
当我要销毁包含的控件的实例时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);
}
Run Code Online (Sandbox Code Playgroud)
在哪里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;
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
基本上,我做了TextBox
应该做的事情.最后我也打电话GC.Collect()
(不是绝对必要但有助于更快地释放内存).这是一个非常难看的解决方案,但它似乎解决了这个问题.不再TextEditors
卡在终结队列中.
事实上,PresentationFramework.dll!System.Windows.Documents.TextEditor
它有一个终结器,因此除非正确处理,否则它会卡在终结器队列中(连同挂在其上的所有内容)。我四处搜寻了一下PresentationFramework.dll
,不幸的是我不知道如何让TextBox
es 处理它们附加的TextEditor
s。唯一相关的调用TextBox.OnDetach
是在TextBoxBase.InitializeTextContainer()
. 在那里您可以看到,一旦 aTextBox
创建了 a TextEditor
,它只会处理它以换取创建一个新的。自行处置的另外两个条件TextEditor
是应用程序域卸载或 WPF 调度程序关闭。前者看起来更有希望,因为我发现无法重新启动已关闭的 WPF 调度程序。WPF 对象不能直接跨应用程序域共享,因为它们不是派生自 的MarshalByRefObject
,但 Windows 窗体控件可以。尝试将您的ElementHost
应用程序域放在一个单独的应用程序域中,并在清除表单时将其拆除(您可能需要先关闭调度程序)。另一种方法是使用 MAF 加载项将 WPF 控件放入不同的应用程序域中;看到这个问题。
归档时间: |
|
查看次数: |
2335 次 |
最近记录: |