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ółówNiektó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.
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ę.