Я свел проблему, которую вижу в одном из моих приложений, к невероятно простому образцу воспроизведения. Мне нужно знать, что-то не так или я что-то упускаю.
В любом случае, ниже приведен код. Поведение заключается в том, что код выполняется и постоянно растет в памяти, пока не завершится с OutOfMemoryException. Это занимает некоторое время, но поведение состоит в том, что объекты распределяются и не подвергаются сборке мусора.
Я взял дампы памяти и запустил! Gcroot на некоторых вещах, а также использовал ANTS, чтобы выяснить, в чем проблема, но я занимался этим некоторое время и мне нужны новые глаза.
Этот образец воспроизведения представляет собой простое консольное приложение, которое создает Canvas и добавляет к нему строку. Это делает это постоянно. Это все, что делает код. Время от времени он спит, чтобы гарантировать, что процессор не будет облагаться налогом так, что ваша система не отвечает (и чтобы убедиться, что нет никакой странности с невозможностью работы GC).
У кого-нибудь есть мысли? Я пробовал это только с .NET 3.0, .NET 3.5, а также .NET 3.5 с пакетом обновления 1 (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 не запускается в основном потоке моего консольного приложения, тем более что я работаю на двухъядерном компьютере, что означает, что Concurrent Workstation GC активен. Сообщение сообщения, однако, да.
Чтобы доказать это, вот версия приложения WPF, которая запускает тест на DispatcherTimer. Он выполняет 1000 итераций в течение интервала таймера 100 мс. Более чем достаточно времени для обработки любых сообщений из насоса и поддержания низкой загрузки ЦП.
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. Я использовал код из первого ответа, и моя память росла очень медленно. Обратите внимание, что 1 мс намного медленнее и меньше итераций, чем в моем примере. Вы должны дать ему поработать пару минут, прежде чем вы начнете замечать рост. Через 5 минут он на 46 МБ от начальной точки 30 МБ.
ПРИМЕЧАНИЕ 3. Удаление звонка .Arrange полностью исключает рост. К сожалению, этот вызов очень важен для моего использования, поскольку во многих случаях я создаю PNG-файлы из Canvas (через класс RenderTargetBitmap). Без вызова .Arrange он вообще не размещает холст.