Przeciek uchwytu GDI przy użyciu TGIFImage w drugim wątku

Mam wątek w tle, który ładuje obrazy (z dysku lub serwera), aby ostatecznie przekazać je do głównego wątku w celu narysowania. Gdy ten drugi wątek ładuje obrazy GIF przy użyciu VCLTGIFImage klasa, ten programczasami przecieka kilka uchwytów za każdym razem, gdy w wątku wykonuje się następujący wiersz:

<code>m_poBitmap32->Assign(poGIFImage);
</code>

Oznacza to, że właśnie otwarty obraz GIF jest przypisywany do bitmapy należącej do wątku. Żadne z nich nie są dzielone z innymi wątkami, tj. Są całkowicie zlokalizowane w wątku. Jest zależny od czasu, więc nie występuje za każdym razem, gdy linia jest wykonywana, ale kiedy to się dzieje, dzieje się to tylko w tej linii. Każdy wyciek to jeden DC, jedna paleta i jedna bitmapa. (UżywamGDIView, który daje bardziej szczegółowe informacje GDI niż Process Explorer.)m_poBitmap32 tutaj jestGraphics32 TBitmap32 obiekt, ale odtworzyłem to używając zwykłych klas tylko VCL, tj. używającGraphics::TBitmap::Assign.

W końcu dostajęEOutOfResources wyjątek, prawdopodobnie wskazujący, że sterta pulpitu jest pełna:

<code>: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)
</code>

Jak rozwiązać ten problem i bezpiecznie korzystaćTGIFImage w wątku w tle?

Po drugie, czy napotkam ten sam problem z klasami PNG, JPEG lub BMP? Nie doszedłem do tej pory, ale biorąc pod uwagę, że jest to kwestia wątków / synchronizacji, nie oznacza to, że nie zrobię tego, jeśli użyją podobnego kodu doTGIFImage.

Używam C ++ Builder 2010 (część RAD Studio.)

Więcej szczegółów

Niektóre badania wykazałyNie jestem jedyną osobą, która to spotyka. Aby zacytować jeden wątek,

Pomoc (2007) mówi: W aplikacjach wielowątkowych, które używają blokady do ochrony kanwy, wszystkie połączenia korzystające z kanwy muszą być chronione przez wywołanie funkcji Zablokuj. Każdy wątek, który nie blokuje płótna przed użyciem, wprowadzi potencjalne błędy.

[...]

Ale to stwierdzenie jest absolutnie fałszywe: musisz zablokować płótno w wątku wtórnym, nawet jeśli inne wątki go nie dotykają. W przeciwnym razie uchwyt GDI płótna może zostać zwolniony w głównym wątku jako nieużywany w dowolnym momencie (asynchronicznie).

Inna odpowiedź wskazuje coś podobnego, co może być związane z pamięcią podręczną obiektów GDI w graphics.pas.

To przerażające: obiekt utworzony i wykorzystywany w całości w jednym wątku może mieć asynchronicznie zwolnione niektóre zasoby w głównym wątku. Niestety,Nie wiem, jak zastosować poradę blokadyTGIFImage. TGIFImage nie maCanvas, chociaż maBitmap który ma płótno. Blokowanie bez efektu. Podejrzewam, że problem rzeczywiście istniejeTGIFFrame, klasa wewnętrzna. Nie wiem też, czy i jak powinienem zablokować jakiekolwiek zasoby TBitmap32. Próbowałem przypisać aTMemoryBackend do mapy bitowej, która unika korzystania z GDI, ale nie miała żadnego efektu.

Reprodukcja

Możesz to łatwo odtworzyć. Utwórz nową aplikację VCL i utwórz nową jednostkę, która zawiera wątek. W metodzie Execute wątku umieść ten kod:

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

Możesz użyćGraphics::TBitmap jeśli nie masz zainstalowanej Graphics32.

W głównym formularzu aplikacji dodaj przycisk, który tworzy i uruchamia wątek. Dodaj kolejny przycisk, który wykonuje podobny kod do powyższego (tylko raz, nie ma potrzeby zapętlania. Mine zapisuje TBitmap32 jako zmienną składową zamiast ją tam tworzyć i unieważnia, aby ostatecznie pomalować ją do formularza.) Uruchom program i kliknij przycisk, aby rozpocząć wątek. Prawdopodobnie zobaczysz przecieki obiektów GDI, ale jeśli nie, naciśnij drugi przycisk, który uruchamia podobny kod raz w głównym wątku - raz wystarczy, wydaje się, że coś uruchamia - i będzie wyciekać. Zobaczysz wzrost wykorzystania pamięci i wyciek uchwytów GDI z prędkością kilkudziesięciu na sekundę.

questionAnswers(1)

yourAnswerToTheQuestion