C# 如何以确定的方式释放重载WPF控件使用的内存?

C# 如何以确定的方式释放重载WPF控件使用的内存?,c#,wpf,memory-leaks,garbage-collection,controls,C#,Wpf,Memory Leaks,Garbage Collection,Controls,当分配大量内存的控件使用UI中的输入事件被破坏和重构时,我遇到了一些问题 显然,将窗口的内容设置为null不足以释放包含的控件正在使用的内存。它最终将是GCed。但随着销毁和构造控件的输入事件越来越频繁,GC似乎跳过了一些对象 我将其分解为以下示例: XAML: C#: 使用系统; 使用System.Windows; 使用System.Windows.Controls; 使用System.Windows.Input; 命名空间WpfApplication1 { 公共部分类主窗口:窗口 { 私

当分配大量内存的控件使用UI中的输入事件被破坏和重构时,我遇到了一些问题

显然,将窗口的内容设置为null不足以释放包含的控件正在使用的内存。它最终将是GCed。但随着销毁和构造控件的输入事件越来越频繁,GC似乎跳过了一些对象

我将其分解为以下示例:

XAML:


C#:

使用系统;
使用System.Windows;
使用System.Windows.Controls;
使用System.Windows.Input;
命名空间WpfApplication1
{
公共部分类主窗口:窗口
{
私有类HeavyObject:UserControl
{
专用字节[]x=新字节[750000000];
公共重对象()
{
对于(inti=0;i++<9999999;){}//我们可以在Process Explorer中直观地看到
Content=“Peekaboo!”//将内容更改为el cheapo重新触发MouseMove
}
}
公共主窗口()
{
初始化组件();
//工程1:
//while(true)
//{
//window.Content=null;
//window.Content=新的HeavyObject();
//}
}
私有无效窗口\u MouseMove(对象发送方,MouseEventArgs e)
{
如果(window.Content==null)
{
GC.Collect();
GC.WaitForPendingFinalizers();
//工程2:
//新的重对象();
//window.Content=“Peekaboo!”//将内容更改为el cheapo重新触发MouseMove
//返回;
window.Content=新的HeavyObject();
}
其他的
{
window.Content=null;
}
}
}
}
这会在每个MouseMove事件中分配一个约750MB的对象(HeavyObject),并将其作为主窗口的内容。如果光标保持在窗口上,内容更改将无休止地重新触发事件。 对HeavyObject的引用在其下一次构造之前为空,并且触发了对其遗骸进行GC的徒劳尝试

我们在ProcessExplorer/Taskman中可以看到,有时会分配1.5GB或更多。如果没有,则疯狂移动鼠标,离开并重新进入窗口。如果您为x86编译时没有使用LargeAddressware,则会得到OutOfMemoryException(限制~1.3GB)

如果您在无休止循环中分配HeavyObject而不使用鼠标输入(取消注释部分“Works 1”),或者通过鼠标输入触发分配但不将对象放入可视化树(取消注释部分“Works 2”),则不会遇到此行为

所以我想这与视觉树懒散地释放资源有关。但是还有另一个奇怪的效果:如果光标在窗口外时消耗了1.5GB,那么在再次触发MouseMove之前,GC似乎不会启动。至少,只要不再触发其他事件,内存消耗似乎就会稳定下来。所以要么有一个长期存在的引用留在某个地方,要么GC在没有活动时变得懒惰

我觉得很奇怪。你能知道发生了什么事吗

编辑: 正如BalamBalam所评论的:在销毁控件之前,可以取消对控件背后的数据的引用。这将是B计划。不过,可能有一个更通用的解决方案


说这样的控件是坏代码是没有帮助的。我想知道为什么一个我没有引用的对象在取消引用后,如果我离开UI几秒钟,就会得到GCed,但是如果另一个对象(必须由用户输入创建)立即取代它,它就会永远存在。

好的,我想我可能知道这里发生了什么。不过还是要加一点盐

我稍微修改了你的代码

Xaml

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525" x:Name="window" MouseDown="window_MouseDown">
</Window>
我可以在上面移动鼠标一段时间,但是最终它会抛出一个OutofMemoryException。强制执行异常的一个好方法是执行多个移动,然后尝试最大化窗口

理论

这就是我的理论。由于您的UI线程在消息循环事件(MouseMove)中创建和删除内存,因此垃圾收集器无法跟上。大型对象堆上的垃圾收集已完成。这也是一项昂贵的任务,因此尽可能少地执行

在MouseMove中执行快速单击on/off/on/off时,我们注意到的延迟变得更加明显。如果要分配另一个大对象,根据该MSDN文章,GC将尝试收集(如果内存不可用)或开始使用磁盘

系统处于内存不足的情况:当我收到来自操作系统的高内存通知时,就会发生这种情况。如果我认为执行第2代GC会有成效,我会触发一个

现在这部分很有趣,我猜这里

最后,到目前为止,LOH还没有被压缩为集合的一部分,但这是一个不应该依赖的实现细节。因此,为了确保GC不会移动某些内容,请始终固定它。现在,利用你新发现的LOH知识,去控制堆

因此,如果大对象堆没有被压缩,并且您在一个紧密循环中请求多个大对象,即使理论上有足够的内存,收集是否可能导致碎片最终导致OutOfMemoryException?

解决方案

让我们稍微释放一下UI线程,让GC室呼吸一下。在异步调度程序调用中包装分配代码,并使用低优先级,例如SystemIdle。这样,它只会在UI线程运行时分配内存
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace WpfApplication1
{
    public partial class MainWindow : Window
    {
        private class HeavyObject : UserControl
        {
            private byte[] x = new byte[750000000];

            public HeavyObject()
            {
                for (int i = 0; i++ < 99999999; ) { } // just so we can follow visually in Process Explorer
                Content = "Peekaboo!"; // change Content to el cheapo re-trigger MouseMove
            }
        }

        public MainWindow()
        {
            InitializeComponent();

            //Works 1:
            //while (true)
            //{
            //    window.Content = null;
            //    window.Content = new HeavyObject();
            //}
        }

        private void window_MouseMove(object sender, MouseEventArgs e)
        {
            if (window.Content == null)
            {
                GC.Collect();
                GC.WaitForPendingFinalizers();
                // Works 2:
                //new HeavyObject();
                //window.Content = "Peekaboo!"; // change Content to el cheapo re-trigger MouseMove
                //return;
                window.Content = new HeavyObject();
            }
            else
            {
                window.Content = null;
            }
        }
    }
}
<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525" x:Name="window" MouseDown="window_MouseDown">
</Window>
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace WpfApplication1
{
    public partial class MainWindow : Window
    {
        private class HeavyObject : UserControl
        {
            private byte[] x = new byte[750000000];

            public HeavyObject()
            {
                for (int i = 0; i < 750000000; i++ )
                {                    
                    unchecked
                    {
                        x[i] = (byte)i;       
                    }
                }

                // Release optimisation will not compile the array
                // unless you initialize and use it. 
                Content = "Loadss a memory!! " + x[1] + x[1000]; 
            }
        }

        const string msg = " ... nulled the HeavyObject ...";

        public MainWindow()
        {
            InitializeComponent();
            window.Content = msg;
        }

        private void window_MouseDown(object sender, MouseEventArgs e)
        {
            if (window.Content.Equals(msg))
            {
                window.Content = new HeavyObject();
            }
            else
            {
                window.Content = msg;
            }
        }
    }
}
<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525" x:Name="window" MouseMove="window_MouseDown">
</Window>
    private void window_MouseDown(object sender, MouseEventArgs e)
    {
        Dispatcher.BeginInvoke((ThreadStart)delegate()
            {
                if (window.Content.Equals(msg))
                {
                    window.Content = new HeavyObject();
                }
                else
                {
                    window.Content = msg;
                }
            }, DispatcherPriority.ApplicationIdle);
    }