Утечка обработки GDI с использованием TGIFImage во втором потоке

У меня есть фоновый поток, который загружает изображения (либо с диска, либо с сервера) с целью передачи их основному потоку для отрисовки. Когда этот второй поток загружает GIF-изображения, используя класс VCLTGIFImage, эта программа иногдапропускает несколько дескрипторов каждый раз, когда в потоке выполняется следующая строка:

m_poBitmap32->Assign(poGIFImage);

То есть , только что открытое GIF-изображение назначается растровому изображению, принадлежащему потоку. Ни один из них не используется совместно с другими потоками, т. е. полностью локализован для потока. Это зависит от времени, поэтому не происходит каждый раз, когда выполняется строка, но когда это происходит, это происходит только в этой строке. Каждая утечка — это один DC, одна палитра и одно растровое изображение. (Я использую GDIView, который дает более подробную информацию о GDI, чем Process Explorer.) m_poBitmap32вот объект Graphics32 TBitmap32, но я воспроизвел его с помощью простого VCL. -только классы, т.е. с использованием Graphics::TBitmap::Assign.

В конце концов я получаю исключение EOutOfResources, которое, вероятно, указывает на то, что куча рабочего стола заполнена:

:7671b9bc KERNELBASE.RaiseException + 0x58
:40837f2f ; C:\Windows\SysWOW64\vclimg140.bpl
:40837f68 ; C:\Windows\SysWOW64\vclimg140.bpl
:4084459f ; C:\Windows\SysWOW64\vclimg140.bpl
:4084441a vclimg140.@Gifimg@TGIFFrame@Draw$qqrp16Graphics@TCanvasrx11Types@TRectoo + 0x4a
:408495e2 ; C:\Windows\SysWOW64\vclimg140.bpl
:50065465 rtl140.@Classes@TPersistent@Assign$qqrp19Classes@TPersistent + 0x9
:00401C0E TLoadingThread::Execute(this=:00A44970)

Как решить эту проблему и безопасно использовать TGIFImageв фоновом потоке?

И, во-вторых, столкнусь ли я с той же проблемой с классами PNG, JPEG или BMP? Пока нет, но, учитывая проблему с потоками/синхронизацией, это не значит, что я не буду, если они будут использовать код, аналогичный TGIFImage.

Я использую C++ Builder 2010 (часть RAD Studio.)


Подробнее

Некоторые исследования показали Я не единственный, кто столкнулся с этим.Чтобы процитировать одну ветку,

Help (2007) говорит: В многопоточных приложениях, использующих Lock для защиты холста, все вызовы, использующие холст, должны быть защищены вызовом метода Замок. Любой поток, который не блокирует холст перед его использованием, будет представить потенциальные ошибки.

[...]

Но это утверждение абсолютно неверно: вы ДОЛЖНЫ заблокировать холст в вторичный поток, даже если другие потоки его не касаются. В противном случае дескриптор GDI холста может быть освобожден в основном потоке как неиспользуемый в любой момент. момент (асинхронно).

Другой ответ указывает на что-то подобное, что может быть связано с кэшем объектов GDI в Graphics.pas.

Это пугает: объект, созданный и используемый полностью в одном потоке, может асинхронно освобождать часть своих ресурсов в основном потоке. К сожалению, я не знаю, как применить совет блокировки к TGIFImage.TGIFImageне имеет Canvas, хотя у него есть Bitmapс холстом. Блокировка, которая не имеет никакого эффекта. Я подозреваю, что проблема на самом деле в TGIFFrame, внутреннем классе. Я также не знаю, следует ли и как заблокировать какие-либо ресурсы TBitmap32. Я попытался назначить TMemoryBackendрастровому изображению, что позволяет избежать использования GDI, но это не дало никакого эффекта.

Репродукция

Воспроизвести это очень легко. Создайте новое приложение VCL и создайте новый модуль, содержащий поток. В методе Execute потока поместите этот код:

while (!Terminated) {
    TGraphic* poGraphic = new TGIFImage();
    TBitmap32* poBMP32 = new TBitmap32();
    __try {
        poGraphic->LoadFromFile(L"test.gif");
        poBMP32->Assign(poGraphic);
    } __finally {
        delete poBMP32;
        delete poGraphic;
    }
}

Вы можете использовать Graphics::TBitmap, если у вас не установлен Graphics32.

В главной форме приложения добавьте кнопку, которая создает и запускает цепочку. Добавьте еще одну кнопку, которая выполняет код, аналогичный приведенному выше (только один раз, нет необходимости зацикливаться. Мой также сохраняет TBitmap32 как переменную-член вместо того, чтобы создавать ее там, и делает ее недействительной, поэтому в конечном итоге она нарисует ее в форму.) Запустите программу и нажмите кнопку, чтобы начать цепочку. Вероятно, вы уже увидите утечку объектов GDI, но если не нажать вторую кнопку, запускающую аналогичный код один раз в основном потоке — достаточно одного раза, вроде что-то срабатывает — и утечка. Вы увидите рост использования памяти и утечку дескрипторов GDI со скоростью несколько десятков в секунду.

16
задан David 19 April 2012 в 17:29
поделиться