我已经将我在其中一个应用程序中看到的一个问题归结为一个非常简单的复制样本.我需要知道是否有什么不对劲或我遗漏的东西.
无论如何,下面是代码.行为是代码在内存中运行并稳定增长,直到它与OutOfMemoryException崩溃.这需要一段时间,但行为是对象被分配,而不是垃圾收集.
我已经采取内存转储并运行!gcroot对某些事情以及使用ANTS来弄清楚问题是什么,但我已经有一段时间了,需要一些新的眼睛.
此复制示例是一个简单的控制台应用程序,可创建Canvas并为其添加一行.它不断地这样做.这就是所有的代码.它不时地睡觉,以确保CPU不会因为你的系统没有响应而被征税(并且确保GC无法运行并不奇怪).
有人有什么想法?我已经尝试过仅使用.NET 3.0,.NET 3.5以及.NET 3.5 SP1,并且在所有三种环境中都会出现相同的行为.
另请注意,我已将此代码放在WPF应用程序项目中,并通过单击按钮触发代码,它也会出现在那里.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows.Controls; using System.Windows.Shapes; using System.Windows; namespace SimplestReproSample { class Program { [STAThread] static void Main(string[] args) { long count = 0; while (true) { if (count++ % 100 == 0) { // sleep for a while to ensure we aren't using up the whole CPU System.Threading.Thread.Sleep(50); } BuildCanvas(); } } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] private static void BuildCanvas() { Canvas c = new Canvas(); Line line = new Line(); line.X1 = 1; line.Y1 = 1; line.X2 = 100; line.Y2 = 100; line.Width = 100; c.Children.Add(line); c.Measure(new Size(300, 300)); c.Arrange(new Rect(0, 0, 300, 300)); } } }
注意:下面的第一个答案有点偏离基础,因为我已明确声明在WPF应用程序的按钮单击事件期间发生了同样的行为.然而,我没有明确说明在那个应用程序中我只进行了有限数量的迭代(比如1000).这样做可以让GC在您单击应用程序时运行.另请注意,我明确表示我已经进行了内存转储,并发现我的对象是通过!gcroot生根的.我也不同意GC无法运行.GC不在我的控制台应用程序的主线程上运行,特别是因为我在双核机器上,这意味着并发工作站GC处于活动状态.但是,消息泵是的.
为了证明这一点,这是一个在DispatcherTimer上运行测试的WPF应用程序版本.它在100ms定时器间隔期间执行1000次迭代.有足够的时间处理泵中的任何消息并保持低CPU使用率.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Shapes; namespace SimpleReproSampleWpfApp { public partial class Window1 : Window { private System.Windows.Threading.DispatcherTimer _timer; public Window1() { InitializeComponent(); _timer = new System.Windows.Threading.DispatcherTimer(); _timer.Interval = TimeSpan.FromMilliseconds(100); _timer.Tick += new EventHandler(_timer_Tick); _timer.Start(); } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] void RunTest() { for (int i = 0; i < 1000; i++) { BuildCanvas(); } } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] private static void BuildCanvas() { Canvas c = new Canvas(); Line line = new Line(); line.X1 = 1; line.Y1 = 1; line.X2 = 100; line.Y2 = 100; line.Width = 100; c.Children.Add(line); c.Measure(new Size(300, 300)); c.Arrange(new Rect(0, 0, 300, 300)); } void _timer_Tick(object sender, EventArgs e) { _timer.Stop(); RunTest(); _timer.Start(); } } }
注2:我使用了第一个答案的代码,我的记忆增长得非常慢.请注意,1ms比我的示例慢得多且迭代次数少.在开始注意到增长之前,你必须让它运行几分钟.5分钟后,从30MB的起点开始,它的容量为46MB.
注意3:删除对.Arrange的调用完全消除了增长.不幸的是,这个调用对我的使用非常重要,因为在很多情况下我是从Canvas创建PNG文件(通过RenderTargetBitmap类).如果没有调用.Arrange,它根本不会布局画布.
我能够使用您提供的代码重现您的问题.内存不断增长,因为Canvas对象永远不会被释放; 内存分析器指示Dispatcher的ContextLayoutManager全部保留它们(以便它可以在必要时调用OnRenderSizeChanged).
似乎一个简单的解决方法是添加
c.UpdateLayout()
到了最后BuildCanvas
.
那说,注意Canvas
是UIElement
; 它应该在UI中使用.它不是设计用作任意绘图表面.正如其他评论者已经指出的那样,创建数千个Canvas对象可能表明存在设计缺陷.我意识到你的生产代码可能更复杂,但如果它只是在画布上绘制简单的形状,基于GDI +的代码(即System.Drawing类)可能更合适.