У меня есть процесс, который постоянно выполняет что-то в цикле. Это открытие форм, обработка данных в них и затем их закрытие. Через некоторое время процесс начинает получать ошибки и в конечном итоге завершается сбоем.
Заглянув в диспетчер задач, я заметил, что процесс достигает предела в 10 000 объектов GDI. В этом процессе где-то должна быть утечка объекта GDI.
Я скачал GDIView.exe и заметил, что столбец «Все GDI» со временем продолжает увеличиваться, но количество перечисленных типов объектов GDI разумно и не увеличивается.
Например, смотрите скриншот ниже. GDIView.exe показывает 1782 объекта «Другие GDI». В остальных столбцах показаны 4 растровых изображения, 1 область и 0 других типов объектов:
Я загрузил сценарий «DumpGdi.txt», чтобы помочь WinDbg отображать количество объектов GDI в дампе памяти. В любой момент времени в списке насчитывается менее 100 объектов GDI, несмотря на то, что диспетчер задач и GDIView.exe показывают большое количество объектов GDI.
Какие объекты GDI накапливаются в столбце «Все GDI» в GDIView.exe? Как мне сделать их более заметными? Я хотел бы знать, что происходит, чтобы знать, что искать в моем коде.
Диспетчер задач Windows также показывает такое же количество объектов GDI.
Я не вижу утечек памяти управляемых объектов. Если бы существовали управляемые объекты, я мог бы увидеть их, если бы запустил команду !DumpObj -type в WinDbg, и я мог бы использовать !GCRoot, чтобы выяснить, почему произошла утечка данных. Я также ожидал бы увидеть большое количество объектов Icon, Font и, возможно, Bitmap, но эти типы не представляют собой проблему в выходных данных GDIView.exe.
Я запустил LeakTrack.exe в DebugDiag 2.x. Вывод отчета оказался не очень полезным, за исключением того, что источником утечки могла быть среда CLR.
Я обнаружил проблему с помощью инструмента Microsoft Performance HUD. Когда я запустил тестовое приложение с помощью Performance HUD, инструмент показал мне, что проблема заключалась в утечке как пользовательских объектов CURSOR, так и объектов PAL GDI.
Вкладка «Стек вызовов» показала мне, что основной причиной утечки была следующая строка кода, которая преобразует растровый объект в значок. Эта строка создает временный дескриптор GDI, который необходимо удалить.
this.Icon = Icon.FromHandle(bitmap.GetHicon());
Исправление состоит в том, чтобы избавиться от временного дескриптора:
[System.Runtime.InteropServices.DllImport("user32.dll", CharSet = CharSet.Auto)]
extern static bool DestroyIcon(IntPtr handle);
IntPtr handle = IntPtr.Zero;
try
{
handle = bitmap.GetHicon();
this.Icon = Icon.FromHandle(handle);
}
finally
{
if (handle != IntPtr.Zero)
{
DestroyIcon(handle);
}
}