delphi - GDI maneja la fuga usando TGIFImage en un segundo hilo
thread-safety c++builder (1)
Desafortunadamente, la solución es muy, muy fea. La idea básica es que el hilo de fondo debe adquirir un bloqueo que el hilo principal mantiene cuando está entre mensajes.
La implementación ingenua es así:
- Bloqueo lienzo mutex.
- Hilo de fondo de engendro.
- Espere el mensaje.
- Soltar lienzo mutex.
- Mensaje de proceso
- Bloqueo lienzo mutex.
- Vaya al paso 3.
Tenga en cuenta que esto significa que el hilo de fondo solo puede acceder a los objetos GDI mientras el hilo principal está ocupado, no mientras espera un mensaje. Y esto significa que el subproceso de fondo no puede poseer ningún sondeo mientras no contenga el mutex. Estos dos requisitos tienden a ser demasiado dolorosos. Así que es posible que necesite refinar el algoritmo.
Una mejora es hacer que el hilo de fondo envíe un mensaje al hilo principal cuando necesite usar un lienzo. Esto hará que el subproceso principal libere más rápidamente el mutex del lienzo para que el subproceso de fondo pueda obtenerlo.
Creo que esto será suficiente para que renuncies a esta idea. En su lugar, tal vez, lea el archivo del subproceso de fondo, pero procéselo en el hilo principal.
Tengo un hilo de fondo que carga imágenes (desde un disco o un servidor), con el objetivo de pasarlas al hilo principal para dibujar. Cuando este segundo hilo está cargando imágenes GIF utilizando la clase TGIFImage
de VCL, este programa a veces TGIFImage
varios manejadores cada vez que la siguiente línea se ejecuta en el hilo:
m_poBitmap32->Assign(poGIFImage);
Es decir, la imagen GIF recién abierta se asigna a un mapa de bits que es propiedad del hilo. Ninguno de estos se comparte con ningún otro subproceso, es decir, está completamente localizado en el subproceso. Es dependiente del tiempo, por lo que no ocurre cada vez que se ejecuta la línea, pero cuando ocurre ocurre solo en esa línea. Cada fuga es una DC, una paleta y un mapa de bits. (Uso GDIView , que proporciona información GDI más detallada que Process Explorer). m_poBitmap32
aquí es un objeto Graphics32 TBitmap32 , pero lo he reproducido utilizando clases simples solo para VCL, es decir, utilizando Graphics::TBitmap::Assign
.
Finalmente, obtengo una excepción EOutOfResources
, que probablemente indique que el montón del escritorio está lleno:
: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)
¿Cómo TGIFImage
esto y uso con seguridad TGIFImage
en un hilo de fondo?
Y en segundo lugar, ¿tendré este mismo problema con las clases PNG, JPEG o BMP? No lo he hecho hasta ahora, pero dado que es un problema de subprocesos / sincronización, no significa que no lo haré si usan un código similar a TGIFImage
.
Estoy usando C ++ Builder 2010 (parte de RAD Studio).
Más detalles
Algunas investigaciones mostraron que no soy la única persona que encuentra esto . Para citar de un hilo,
La Ayuda (2007) dice: En las aplicaciones de subprocesos múltiples que usan Bloquear para proteger un lienzo, todas las llamadas que usan el lienzo deben estar protegidas por una llamada a Bloquear. Cualquier hilo que no bloquee el lienzo antes de usarlo introducirá posibles errores.
[...]
Pero esta afirmación es absolutamente falsa: DEBE bloquear el lienzo en un hilo secundario, incluso si otros hilos no lo tocan. De lo contrario, el identificador GDI del lienzo se puede liberar en el hilo principal como no utilizado en cualquier momento (de forma asíncrona).
Otra respuesta indica algo similar, que puede tener que ver con el caché de objetos GDI en graphics.pas.
Eso da miedo: un objeto creado y utilizado completamente en un hilo puede tener algunos de sus recursos liberados de forma asíncrona en el hilo principal. Desafortunadamente, no sé cómo aplicar el consejo de bloqueo a TGIFImage
. TGIFImage
no tiene Canvas
, aunque sí tiene un Bitmap
que tiene un canvas. Bloqueo que no tiene efecto. Sospecho que el problema está realmente en TGIFFrame
, una clase interna. Tampoco sé si o cómo debo bloquear los recursos de TBitmap32. TMemoryBackend
asignar un TMemoryBackend
al mapa de bits, que evita el uso de GDI, pero no tuvo ningún efecto.
Reproducción
Puedes reproducir esto muy fácilmente. Cree una nueva aplicación VCL y cree una nueva unidad que contenga un hilo. En el método de ejecución del hilo, coloque este código:
while (!Terminated) {
TGraphic* poGraphic = new TGIFImage();
TBitmap32* poBMP32 = new TBitmap32();
__try {
poGraphic->LoadFromFile(L"test.gif");
poBMP32->Assign(poGraphic);
} __finally {
delete poBMP32;
delete poGraphic;
}
}
Puede usar Graphics::TBitmap
si no tiene Graphics32 instalado.
En el formulario principal de la aplicación, agregue un botón que cree e inicie el hilo. Agregue otro botón que ejecute un código similar al anterior (solo una vez, no es necesario realizar un bucle. Mine también almacena el TBitmap32 como una variable miembro en lugar de crearlo allí, e invalida para que finalmente lo dibuje en el formulario). Ejecute el programa y haga clic en el botón para iniciar el hilo. Probablemente ya verás una fuga de objetos GDI, pero si no, presiona el segundo botón que ejecuta el código similar una vez en el hilo principal (una vez es suficiente, parece que se activa algo) y se producirá una fuga. Verá un aumento en el uso de la memoria, y que pierde los controles de GDI a una velocidad de varias docenas por segundo.