c# – 重型WPF控件如何以确定的方式使用一个空闲内存?
当使用来自UI的输入事件来破坏和重建控件分配大量内存时,我遇到了问题.
显然,将Window的Content设置为null不足以释放所包含的Control正在使用的内存.它最终会被GCed.但是随着用于销毁和构造控件的输入事件变得更频繁,GC似乎跳过了一些对象. 我把它分解为这个例子: 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" MouseMove="window_MouseMove"> </Window> C#: 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; } } } } 这会在每个MouseMove事件中分配一个~750MB对象(HeavyObject),并将其作为主窗口的内容.如果Cursor在窗口中保持静止,则内容更改将无休止地重新触发该事件. 我们在ProcessExplorer / Taskman中可以看到,有时分配1.5GB或更多.如果没有,请疯狂移动鼠标并离开并重新进入窗口.如果你在没有LARGEADDRESSAWARE的情况下编译x86,你将得到一个OutOfMemoryException(限制~1.3GB). 如果您在不使用鼠标输入的情况下在无限循环中分配HeavyObject(取消注释“Works 1”部分),或者通过鼠标输入触发分配但不将对象放入可视树中,则不会遇到此行为(取消注释) seciton“作品2”). 所以我认为它与视觉树懒惰地释放资源有关.但是还有一个奇怪的效果:如果光标在窗口之外消耗了1.5GB,那么在MouseMove再次被触发之前,GC似乎不会启动.至少,只要没有触发其他事件,内存消耗似乎就会稳定下来.所以要么在某个地方留下长寿命的参考,要么在没有活动时GC变得懒惰. 对我来说似乎很奇怪.你能弄清楚发生了什么吗? 编辑: 说这样的控件是坏代码是没有用的.我想知道为什么我没有引用的对象获得GCed如果我在解除引用之后将UI单独留下几个Ticks,但是如果另一个(必须由用户输入创建)立即获取它,则永远存在地点. 解决方法
好吧,我想我可能知道这里发生了什么.拿一小撮盐虽然……
我稍微修改了你的代码. 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> 代码背后 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; } } } } 现在运行它并单击窗口.我修改了代码,不使用GC.Collect()并在mousedown上设置内存.我正在发布模式下编译并在没有附加调试器的情况下运行. 在窗口上慢慢单击. 第一次点击,你看到内存使用量增加了750MBytes.随后的点击切换消息之间..取消重物..并加载内存!只要你慢慢点击.分配和释放内存时会有轻微的延迟.内存使用量不应超过750MBytes,但是当重对象被清空时它也不会减少.这是设计为GC Large Object Heap is lazy and collects large object memory when new large object memory is needed.从MSDN:
单击窗口上的快速 大约20次点击.怎么了?在我的PC上,内存使用量没有增加,但主窗口现在不再更新消息了.它冻结了.我没有内存不足异常,我的系统的整体内存使用率保持不变. 在Window上使用MouseMove强调系统 现在,通过更改此Xaml,将MouseDown替换为MouseMove事件(根据您的问题代码): <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> 我可以将鼠标移动它很长一段时间但最终它会抛出一个OutofMemoryException.强制异常的一个好方法是多次移动然后尝试最大化窗口. 理论 好的,这就是我的理论.由于您的UI线程在消息循环事件(MouseMove)内创建和删除内存时运行平稳,因此垃圾收集器无法跟上.大对象堆上的垃圾收集在when memory is needed完成.它也是一项昂贵的任务,因此尽可能不经常执行. 当在MouseMove中执行时,我们在快速单击开/关/开/关时注意到的滞后变得更加明显.如果要分配另一个大对象,根据该MSDN文章,GC将尝试收集(如果内存不可用)或将开始使用磁盘.
现在这部分很有意思,我猜这里,但是
因此,如果大对象堆未被压缩,并且您在紧密循环中请求多个大对象,那么即使理论上应该有足够的内存,集合是否可能导致碎片最终导致OutOfMemoryException? 解决方案 让我们释放一下UI线程让GC室呼吸.将分配代码包装在异步Dispatcher调用中并使用低优先级,例如SystemIdle.这样它只会在UI线程空闲时分配内存. 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); } 使用这个我可以在整个表单上跳过鼠标指针,它永远不会抛出OutOfMemoryException.无论是真正的工作还是“修复”了我不知道的问题,但它值得测试. 结论 >请注意,此大小的对象将在大对象堆上分配 >在后台线程中对您的大对象进行分组,以便从UI线程中解耦>要允许GC室呼吸,请使用Dispatcher解除分配,直到应用程序空闲 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |