我有一个后台线程来加载图像(来自磁盘或服务器),目的是最终将它们传递给主线程来绘制。当第二个线程使用VCL的TGIFImage
class加载GIF图像时,每次在线程中执行以下行时,此程序有时会泄漏几个句柄:
m_poBitmap32->Assign(poGIFImage);
也就是说,刚刚打开的GIF图像被分配给线程拥有的位图。这些都不与任何其他线程共享,即完全本地化到线程。它与时序有关,因此每次执行该行时都不会发生,但是当它确实发生时,它只发生在该行上。每个泄漏都是一个DC,一个调色板和一个位图。 (我使用GDIView,它提供比Process Explorer更详细的GDI信息。)m_poBitmap32
这里是一个Graphics32 TBitmap32对象,但我使用普通的仅VCL类重现了这一点,即使用{ {1}}。
最终我收到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。引用一个帖子,
帮助(2007)说: 在使用Lock保护画布的多线程应用程序中,所有使用画布的调用都必须通过调用来保护 锁。在使用之前没有锁定画布的任何线程都将 引入潜在的错误。
[...]
但这句话是绝对错误的:你必须锁定画布 辅助线程,即使其他线程不接触它。否则 canvas的GDI句柄可以在主线程中释放为任何未使用的 时刻(异步)。
另一个回复表明类似的东西,它可能与graphics.pas中的GDI对象缓存有关。
这很可怕:在一个线程中完全创建和使用的对象可以在主线程中异步释放一些资源。不幸的是,我不知道如何将锁定建议应用于TGIFImage
。 TGIFImage
没有TGIFImage
,但它确实有Canvas
它有一个画布。锁定无效。我怀疑问题实际上是Bitmap
,一个内部类。我也不知道是否或如何锁定任何TBitmap32资源。我确实尝试为位图分配TGIFFrame
,这避免了使用GDI,但它没有效果。
您可以非常轻松地重现这一点。创建一个新的VCL应用程序,并创建一个包含线程的新单元。在线程的Execute方法中,放置以下代码:
TMemoryBackend
如果您没有安装Graphics32,可以使用while (!Terminated) {
TGraphic* poGraphic = new TGIFImage();
TBitmap32* poBMP32 = new TBitmap32();
__try {
poGraphic->LoadFromFile(L"test.gif");
poBMP32->Assign(poGraphic);
} __finally {
delete poBMP32;
delete poGraphic;
}
}
。
在应用程序的主窗体中,添加一个创建并启动线程的按钮。添加另一个按钮,执行与上面相似的代码(只需一次,无需循环。我也将TBitmap32存储为成员变量而不是在那里创建它,并使其无效,最终将其绘制到表单中。)运行程序然后单击按钮以启动该线程。您可能会看到GDI对象已经泄漏,但如果没有按下在主线程中运行类似代码的第二个按钮 - 一次就足够了,它似乎会触发某些东西 - 它会泄漏。您将看到内存使用量上升,并且它以每秒几十个的速率泄漏GDI句柄。
答案 0 :(得分:1)
不幸的是,修复非常非常难看。基本思想是后台线程必须获取主线程在消息之间保持的锁。
天真的实现是这样的:
请注意,这意味着后台线程只能在主线程忙时访问GDI对象,而不是在等待消息时。这意味着后台线程不能拥有任何画布,而不支持互斥锁。这两个要求往往太痛苦了。所以你可能需要改进算法。
一个改进是让后台线程在需要使用画布时向主线程发送消息。这将导致主线程更快地释放画布互斥锁,以便后台线程可以获取它。
我认为这足以让你放弃这个想法。相反,也许,从后台线程读取文件,但在主线程中处理它。