GDI с помощью TGIFImage во втором потоке - программирование
Подтвердить что ты не робот

GDI с помощью TGIFImage во втором потоке

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

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 [email protected]@[email protected][email protected]@TRectoo + 0x4a
:408495e2 ; C:\Windows\SysWOW64\vclimg140.bpl
:50065465 [email protected]@[email protected][email protected] + 0x9
:00401C0E TLoadingThread::Execute(this=:00A44970)

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

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

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


Подробнее

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

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

[...]

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

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

Это страшно: объект, созданный и используемый целиком в одном потоке, может выделять некоторые из его ресурсов асинхронно в основном потоке. К сожалению, Я не знаю, как применить рекомендацию Lock к 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.

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

4b9b3361

Ответ 1

К сожалению, исправление очень, очень уродливое. Основная идея заключается в том, что фоновый поток должен получить блокировку, которую основной поток выполняет, когда он находится между сообщениями.

Наивная реализация такова:

  • Блокировка мьютекса холста.
  • Создайте фоновый поток.
  • Ожидание сообщения.
  • Отключить мьютексы холста.
  • Сообщение процесса.
  • Блокировка мьютекса холста.
  • Перейдите к шагу 3.

Обратите внимание, что это означает, что фоновый поток может обращаться только к объектам GDI, пока основной поток занят, а не пока он ждет сообщения. И это означает, что фоновый поток не может владеть какими-либо полосками, в то время как он не поддерживает мьютекс. Эти два требования, как правило, слишком болезненны. Поэтому вам может понадобиться уточнить алгоритм.

Одно уточнение заключается в том, чтобы фоновый поток посылал основному потоку сообщение, когда ему нужно использовать холст. Это приведет к тому, что основной поток быстрее освободит мьютексы холста, чтобы фоновый поток мог его получить.

Я думаю, этого будет достаточно, чтобы заставить вас отказаться от этой идеи. Вместо этого, возможно, прочитайте файл из фонового потока, но обработайте его в основном потоке.