TGIFImage
class加载GIF图像时,该程序有时每次在线程中执行以下行时会泄漏几个句柄:
m_poBitmap32->Assign(poGIFImage);
也就是说,刚刚打开的GIF图像被分配给线程拥有的位图。这些都不与任何其他线程共享,即完全定位到线程。它是时序相关的,所以每次执行时都不会发生,但是当它发生时,它只发生在该行上。每个泄漏都是一个DC,一个调色板和一个位图。 (我使用GDIView,它提供比Process Explorer更详细的GDI信息。)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 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)
其次,我会遇到与PNG,JPEG或BMP类相同的问题吗?我还没有到目前为止,但是考虑到这是一个线程/时序问题,这并不意味着我不会,如果他们使用相似的代码到TGIFImage。
我使用C Builder 2010(RAD Studio的一部分)
更多细节
一些研究显示I’m not the only person to encounter this.从一个线索引用,
Help (2007) says:
In multi-threaded applications that use Lock to protect a canvas,all calls that use the canvas must be protected by a call to
Lock. Any thread that does not lock the canvas before using it will
introduce potential bugs.[…]
But this statement is absolute false: you MUST lock the canvas in
@H_403_35@
secondary thread even if other threads don’t touch it. Otherwise the
canvas’s GDI handle can be freed in main thread as unused at any
moment (asynchronously).另一个回复表示类似的东西,它可能与graphics.pas中的GDI对象缓存有关。
这是可怕的:一个完全在一个线程中创建和使用的对象可以使其某些资源在主线程中异步释放。不幸的是,我不知道如何将锁定建议应用于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; } }如果您没有安装Graphics32,可以使用Graphics :: TBitmap。
在应用程序的主窗体中,添加一个创建和启动线程的按钮。添加另一个执行类似代码的按钮(只需一次,不需要循环),Mine还将TBitmap32存储为成员变量,而不是在其中创建,并且无效,因此最终将其绘制到表单中。)运行程序并点击按钮启动线程。你可能会看到GDI对象已经泄漏了,但是如果没有按下在主线程中运行相似代码的第二个按钮,一次就足够了,它似乎触发了一些东西,并且会泄漏。您将看到内存使用率上升,并且以每秒几十的速率泄漏GDI句柄。