GDI manipular vazamento usando TGIFImage em um segundo segmento
Eu tenho um thread de fundo que carrega imagens (a partir do disco ou um servidor), com o objetivo de, eventualmente, passá-los para o segmento principal para desenhar. Quando este segundo thread está carregando imagens GIF usando o VCLTGIFImage
classe, este programaas vezes vaza várias alças toda vez que a seguinte linha é executada no encadeamento:
<code>m_poBitmap32->Assign(poGIFImage); </code>
Ou seja, a imagem GIF recém aberta está sendo atribuída a um bitmap pertencente ao segmento. Nenhum deles é compartilhado com outros threads, ou seja, são totalmente localizados no thread. É dependente do tempo, por isso não ocorre sempre que a linha é executada, mas quando ocorre, acontece apenas nessa linha. Cada vazamento é um DC, uma paleta e um bitmap. (Eu usoGDIView, que fornece informações de GDI mais detalhadas que o Process Explorer.m_poBitmap32
aqui está umGraphics32 TBitmap32 objeto, mas eu tenho reproduzido isso usando classes simples VCL-only, ou seja, usandoGraphics::TBitmap::Assign
.
Eventualmente eu recebo umEOutOfResources
exceção, provavelmente indicando que o heap da área de trabalho está cheio:
<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>
Como posso resolver isso e usar com segurançaTGIFImage
em um fio de fundo?
E em segundo lugar, vou encontrar esse mesmo problema com as classes PNG, JPEG ou BMP? Eu não tenho até agora, mas dado que é um problema de threading / timing que não significa que eu não vou se eles usam código semelhante aoTGIFImage
.
Eu estou usando o C ++ Builder 2010 (parte do RAD Studio).
Mais detalhesAlgumas pesquisas mostraramEu não sou a única pessoa a encontrar isso. Para citar um segmento,
Ajuda (2007) diz: Em aplicativos multi-threaded que usam bloqueio para proteger uma tela, todas as chamadas que usam a tela devem ser protegidas por uma chamada para bloqueio. Qualquer thread que não bloqueie a tela antes de usá-la, apresentará possíveis bugs.
[...]
Mas essa afirmação é falsa: você DEVE travar a tela no thread secundário mesmo que outros threads não a toquem. Caso contrário, o identificador GDI da tela pode ser liberado no thread principal como não utilizado a qualquer momento (de forma assíncrona).
Outra resposta indica algo semelhante, que pode estar relacionado ao cache de objetos GDI em graphics.pas.
Isso é assustador: um objeto criado e usado inteiramente em um thread pode ter alguns de seus recursos liberados de forma assíncrona no thread principal. Infelizmente,Eu não sei como aplicar o aviso de bloqueio paraTGIFImage
. TGIFImage
não temCanvas
, embora tenha umBitmap
que tem uma tela. Bloqueio que não tem efeito. Eu suspeito que o problema está na verdadeTGIFFrame
, uma aula interna. Eu também não sei se ou como eu deveria bloquear quaisquer recursos TBitmap32. Eu tentei atribuir umTMemoryBackend
para o bitmap, que evita o uso de GDI, mas não teve efeito.
Você pode reproduzir isso com muita facilidade. Crie um novo aplicativo VCL e crie uma nova unidade que contenha um segmento. No método Execute do thread, coloque este código:
<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>
Você pode usarGraphics::TBitmap
se você não tiver o Graphics32 instalado.
No formulário principal do aplicativo, adicione um botão que cria e inicia o segmento. Adicione outro botão que execute um código semelhante ao anterior (uma única vez, sem necessidade de loop. O Mine também armazena o TBitmap32 como uma variável de membro em vez de criá-lo e invalida para que seja eventualmente pintado no formulário). e clique no botão para iniciar o tópico. Você provavelmente verá os objetos GDI vazarem, mas se não pressionar o segundo botão que executa o código semelhante uma vez no thread principal - uma vez é suficiente, parece ativar algo - e ele vazará. Você verá aumento de uso de memória e que ele vaza alças GDI na taxa de várias dezenas por segundo.